├── __mocks__ ├── pm-srp.js ├── pmcrypto.js ├── sieve.js ├── styleMock.js └── fileMock.js ├── containers ├── config │ ├── index.ts │ ├── configContext.ts │ └── Provider.tsx ├── forceRefresh │ ├── index.ts │ ├── context.ts │ └── Provider.tsx ├── members │ ├── MemberName.tsx │ ├── actionHelper.ts │ ├── UsersAndAddressesSection.scss │ ├── index.ts │ └── MemberRole.js ├── authentication │ ├── index.ts │ ├── authenticationContext.ts │ └── Provider.tsx ├── keys │ ├── reactivateKeys │ │ └── reactivateHelper.ts │ ├── calendar │ │ └── index.ts │ ├── shared │ │ └── helper.ts │ ├── index.ts │ └── importKeys │ │ ├── interface.ts │ │ └── state.ts ├── calendar │ ├── exportModal │ │ └── index.ts │ ├── calendarModal │ │ └── index.ts │ ├── importModal │ │ └── index.ts │ ├── notifications │ │ └── index.ts │ ├── index.ts │ ├── settings │ │ ├── CalendarsTable.scss │ │ └── index.ts │ └── shareURL │ │ └── DeleteLinkConfirmationModal.tsx ├── cache │ ├── index.ts │ ├── cacheContext.ts │ └── Provider.tsx ├── importAssistant │ ├── index.ts │ ├── calendar │ │ ├── index.ts │ │ └── interfaces.ts │ ├── contacts │ │ ├── index.ts │ │ └── interfaces.ts │ ├── interfaces.ts │ └── mail │ │ ├── index.ts │ │ ├── modals │ │ └── ImportMailModal.scss │ │ └── constants.ts ├── mail │ ├── index.ts │ └── ModalSettingsLayoutCards.scss ├── autoReply │ ├── index.ts │ ├── AutoReplyForm │ │ ├── AutoReplyFormPermanent.tsx │ │ └── interfaces.ts │ └── InfoLine.tsx ├── earlyAccess │ └── index.ts ├── filters │ ├── modal │ │ ├── advanced │ │ │ └── SieveEditor.scss │ │ └── FilterConditionsFormRow.scss │ ├── spamlist │ │ └── SpamListItem.scss │ └── index.ts ├── api │ ├── apiContext.js │ ├── apiServerTimeContext.ts │ ├── humanVerification │ │ ├── interface.ts │ │ ├── HumanVerificationModal.scss │ │ └── helper.ts │ ├── apiStatusContext.ts │ └── index.ts ├── bridge │ ├── index.ts │ └── ProtonMailBridgeSection.scss ├── illustration │ └── index.ts ├── modals │ ├── modalsContext.js │ ├── childrenContext.js │ ├── index.ts │ └── Children.js ├── contacts │ ├── modals │ │ ├── ContactGroupDetailsModal.scss │ │ └── ContactListModal.scss │ ├── ContactProviderContext.ts │ ├── import │ │ ├── ImportFatalError.ts │ │ └── SelectImportField.tsx │ ├── merge │ │ └── MergeErrorContent.tsx │ ├── ContactGroupDropdown.scss │ ├── widget │ │ └── ContactsWidget.scss │ └── useContact.ts ├── rightToLeft │ ├── context.js │ ├── index.ts │ └── useRightToLeft.js ├── themes │ ├── ThemeCard.scss │ ├── index.ts │ └── ThemeInjector.tsx ├── account │ ├── SettingsLayoutRight.scss │ ├── SettingsLayout.scss │ ├── SettingsSection.tsx │ ├── SettingsSectionWide.tsx │ ├── SettingsLayout.tsx │ ├── SettingsPageTitle.tsx │ ├── SettingsLayoutLeft.tsx │ ├── SettingsSectionTitle.tsx │ ├── SettingsLayoutRight.tsx │ ├── SettingsLayoutLeft.scss │ └── MozillaInfoPanel.tsx ├── password │ └── index.ts ├── sessions │ ├── index.ts │ └── interface.ts ├── support │ ├── index.ts │ └── AuthenticatedBugModal.tsx ├── notifications │ ├── index.ts │ ├── notificationsContext.ts │ └── interfaces.ts ├── organization │ ├── index.ts │ └── helpers │ │ └── organizationKeysHelper.ts ├── features │ └── index.ts ├── logs │ └── index.ts ├── notification │ ├── index.ts │ └── DesktopNotificationSection.tsx ├── payments │ ├── subscription │ │ ├── constants.ts │ │ ├── UpsellItem.tsx │ │ ├── index.ts │ │ └── SubscriptionUpgrade.tsx │ ├── CredisModal.scss │ ├── DonateModal.scss │ ├── BitcoinQRCode.tsx │ ├── GiftCodeSection.scss │ ├── getDefaultCard.ts │ ├── paymentTokenToParams.ts │ ├── useCard.ts │ ├── PaymentInfo.tsx │ └── toDetails.ts ├── search │ └── index.ts ├── overview │ ├── index.ts │ └── LinkItem.tsx ├── messages │ └── index.ts ├── compatibilityCheck │ └── index.ts ├── eventManager │ ├── context.ts │ ├── index.ts │ └── EventManagerProvider.tsx ├── onboarding │ ├── OnboardingStep.tsx │ ├── index.ts │ ├── interface.ts │ └── OnboardingContent.tsx ├── security │ └── index.ts ├── labels │ └── index.ts ├── error │ ├── index.ts │ ├── NotFoundError.tsx │ ├── AccessDeniedError.tsx │ └── InternalServerError.tsx ├── items │ ├── index.ts │ ├── constants.ts │ └── items.scss ├── app │ ├── useOnLogout.ts │ ├── DelinquentContainer.tsx │ ├── LocationErrorBoundary.tsx │ ├── StandardErrorPage.tsx │ ├── interface.ts │ ├── ModalErrorBoundary.tsx │ └── loadEventID.ts ├── login │ ├── index.ts │ ├── FooterDetails.tsx │ └── LoginPasswordInput.tsx ├── paymentMethods │ ├── interface.ts │ └── index.ts ├── invoices │ ├── interface.ts │ ├── index.ts │ └── InvoiceAmount.tsx ├── vpn │ ├── OpenVPNConfigurationSection │ │ ├── utils.js │ │ ├── ServerNumber.tsx │ │ └── CityNumber.tsx │ └── index.ts ├── resetPassword │ ├── index.ts │ └── ResetTokenInput.tsx ├── addresses │ ├── missingKeys │ │ ├── state.ts │ │ └── interface.ts │ ├── index.ts │ ├── useAddressModel.js │ └── AddressesSection.tsx ├── filePreview │ ├── index.ts │ ├── TextPreview.tsx │ └── PreviewLoader.tsx ├── topBanners │ ├── PublicTopBanners.tsx │ ├── SubUserTopBanner.tsx │ └── index.ts ├── heading │ └── index.ts ├── layouts │ ├── index.ts │ └── DensityInjector.tsx ├── general │ └── index.ts └── domains │ └── CatchAllSection.tsx ├── components ├── avatar │ ├── index.ts │ └── Avatar.tsx ├── vr │ ├── index.ts │ └── Vr.tsx ├── badge │ └── index.ts ├── card │ ├── index.ts │ └── Card.tsx ├── label │ ├── index.ts │ └── Label.tsx ├── price │ └── index.ts ├── time │ └── index.ts ├── option │ └── index.ts ├── select │ └── index.ts ├── timezoneSelector │ └── index.ts ├── toggle │ └── index.ts ├── wizard │ └── index.ts ├── fileIcon │ └── index.ts ├── stepDot │ └── index.ts ├── stepDots │ └── index.ts ├── tooltip │ └── index.ts ├── breadcrumb │ └── index.ts ├── countdown │ └── index.ts ├── focus │ └── index.ts ├── labelStack │ └── index.ts ├── paragraph │ ├── index.ts │ └── Paragraph.js ├── color │ └── index.ts ├── contextMenu │ └── index.ts ├── scroll │ ├── index.ts │ └── ScrollShadows.tsx ├── tabs │ ├── index.ts │ └── index.d.ts ├── attachedFile │ └── index.ts ├── editableText │ └── index.ts ├── ellipsis │ └── index.ts ├── truncatedText │ └── index.ts ├── fileNameDisplay │ └── index.ts ├── dragMoveContainer │ ├── index.ts │ └── DragMoveContainer.tsx ├── dropzone │ ├── index.ts │ ├── helpers.ts │ └── Dropzone.scss ├── collapsingBreadcrumbs │ ├── index.ts │ └── interfaces.ts ├── selectTwo │ ├── select.ts │ └── index.ts ├── title │ ├── index.ts │ ├── Title.tsx │ └── SubTitle.tsx ├── input │ ├── LayoutCard.scss │ ├── TelInput.js │ ├── RichTextEditor │ │ ├── quill │ │ │ └── bubble │ │ │ │ └── _toolbar.scss │ │ └── RichTextEditor.js │ ├── TwoFactorInput.tsx │ ├── LegacyInputField.tsx │ ├── EmailInput.tsx │ ├── LazyRichTextEditor.js │ ├── Radio.tsx │ └── useDebounceInput.ts ├── image │ ├── index.ts │ └── QRCode.tsx ├── miniCalendar │ ├── index.d.ts │ └── index.ts ├── shortcuts │ ├── ShortcutsModal.scss │ └── index.ts ├── addressesAutomplete │ └── index.ts ├── toolbar │ ├── ToolbarSeparator.tsx │ ├── index.ts │ └── Toolbar.tsx ├── version │ ├── index.ts │ └── ChangeLogModal.scss ├── alert │ ├── index.ts │ └── DoNotWindowOpenAlertError.tsx ├── treeview │ ├── index.ts │ └── TreeViewContainer.tsx ├── orderable │ ├── OrderableElement.js │ ├── OrderableHandle.js │ ├── OrderableContainer.js │ └── index.js ├── orderableTable │ ├── OrderableTableHeader.scss │ ├── index.ts │ ├── OrderableTableBody.js │ └── OrderableTable.scss ├── editor │ ├── index.ts │ ├── toolbar │ │ └── SquireToolbarSeparator.tsx │ └── interface.ts ├── popper │ ├── index.ts │ ├── usePopperAnchor.ts │ └── Popper.tsx ├── text │ ├── Legend.tsx │ ├── index.ts │ ├── Preformatted.tsx │ └── ErrorZone.tsx ├── globalLoader │ ├── index.tsx │ └── GlobalLoader.tsx ├── contacts │ ├── index.ts │ ├── ContactSummary.scss │ ├── EncryptedIcon.tsx │ ├── ContactGroupIcon.tsx │ ├── ContactImageField.tsx │ └── ContactLabelProperty.tsx ├── loader │ ├── LoaderIcon.js │ ├── index.ts │ ├── useLoader.js │ ├── TextLoader.js │ ├── Loader.tsx │ └── EllipsisLoader.tsx ├── pagination │ ├── index.ts │ └── usePaginationAsync.ts ├── icon │ ├── index.ts │ ├── Icons.tsx │ └── Icon.test.js ├── progress │ ├── index.ts │ └── Progress.tsx ├── challenge │ ├── index.ts │ └── interface.ts ├── portal │ └── Portal.tsx ├── v2 │ ├── index.ts │ └── phone │ │ ├── LazyPhoneInput.tsx │ │ └── flagSvgs.ts ├── autocomplete │ ├── index.ts │ └── SimpleAutocomplete.tsx ├── logo │ ├── MainLogo.tsx │ └── index.ts ├── layout │ ├── index.ts │ └── interface.ts ├── topnavbar │ ├── index.ts │ ├── TopNavbarListItem.tsx │ ├── TopNavbar.tsx │ └── TopNavbarList.tsx ├── container │ ├── Field.js │ ├── Block.js │ ├── ProminentContainer.tsx │ ├── index.ts │ ├── Bordered.js │ ├── EditableSection.tsx │ ├── Summary.tsx │ └── Row.tsx ├── link │ ├── LearnMore.tsx │ ├── index.ts │ ├── Href.tsx │ ├── LearnMore.test.js │ └── Href.test.js ├── button │ ├── ErrorButton.tsx │ ├── LinkButton.tsx │ ├── PrimaryButton.tsx │ ├── Button.tsx │ ├── InlineLinkButton.tsx │ ├── FloatingButton.tsx │ └── index.ts ├── header │ └── Header.tsx ├── dropdown │ ├── DropdownCaret.tsx │ ├── index.ts │ └── useIsClosing.ts ├── table │ ├── TableBody.tsx │ ├── TableRowBusy.tsx │ ├── TableCellBusy.tsx │ ├── TableFooter.tsx │ ├── index.ts │ ├── TableRow.tsx │ ├── Table.tsx │ ├── TableCell.tsx │ └── TableHeader.tsx ├── sidebar │ ├── SidebarListItemContentIcon.tsx │ ├── SidebarListItemDiv.tsx │ ├── SidebarNav.tsx │ ├── MobileNavLink.tsx │ ├── SidebarListItemHeaderButton.tsx │ ├── MobileNavServices.tsx │ ├── SidebarListItemButton.tsx │ ├── SidebarPrimaryButton.tsx │ ├── SidebarListItem.tsx │ ├── SidebarListItemHeaderLink.tsx │ ├── SidebarBackButton.tsx │ └── SidebarListItemContent.tsx └── modal │ ├── DeleteModal.tsx │ ├── BackButton.tsx │ ├── FormModal.test.js │ ├── Inner.tsx │ ├── Title.tsx │ └── Footer.tsx ├── helpers ├── index.ts ├── appVersion.ts └── component.ts ├── hooks ├── helpers │ ├── vendorEncoder.ts │ └── createModelHook.ts ├── useFilters.js ├── useContacts.js ├── useContactEmails.js ├── useMessageCounts.js ├── usePaymentMethods.js ├── useModals.ts ├── useApiStatus.ts ├── useConfig.ts ├── useConversationCounts.js ├── useForceRefresh.ts ├── useApiServerTime.ts ├── useApps.ts ├── useDomains.ts ├── useApi.ts ├── usePrevious.ts ├── useUserSettings.ts ├── useInstance.ts ├── useLoginType.tsx ├── useEventManager.ts ├── useMainArea.ts ├── useDocumentTitle.ts ├── useImporters.ts ├── useIsMounted.ts ├── useNotifications.tsx ├── useToggle.ts ├── useStep.ts ├── useCache.ts ├── useKeyPress.ts ├── useAuthentication.ts ├── useSynchronizingState.ts ├── useAppTitle.ts ├── useLocalState.ts ├── useFeature.ts ├── useCyberMondayPeriod.ts ├── useSvgGraphicsBbox.ts ├── useBlackFridayPeriod.ts ├── useContactEmailsSortedByName.ts ├── usePremiumDomains.js ├── useProductPayerPeriod.ts ├── useActiveWindow.ts ├── useGetUserKeysRaw.ts ├── useUserScopes.ts ├── useBeforeUnload.ts ├── useClickOutside.ts └── useControlled.ts ├── tsconfig.json ├── .gitlab-ci.yml ├── index.ts ├── typings ├── css.d.ts └── index.d.ts ├── rtl.setup.js ├── .prettierrc ├── babel.config.js ├── .editorconfig ├── .gitignore ├── jest.config.js └── .github └── PULL_REQUEST_TEMPLATE.md /__mocks__/pm-srp.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /__mocks__/pmcrypto.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /containers/config/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /containers/forceRefresh/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /containers/members/MemberName.tsx: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /containers/authentication/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /containers/members/actionHelper.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /containers/keys/reactivateKeys/reactivateHelper.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/avatar/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Avatar'; 2 | -------------------------------------------------------------------------------- /components/vr/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Vr } from './Vr'; 2 | -------------------------------------------------------------------------------- /components/badge/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Badge } from './Badge'; 2 | -------------------------------------------------------------------------------- /components/card/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Card } from './Card'; 2 | -------------------------------------------------------------------------------- /components/label/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Label } from './Label'; 2 | -------------------------------------------------------------------------------- /components/price/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Price } from './Price'; 2 | -------------------------------------------------------------------------------- /components/time/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Time } from './Time'; 2 | -------------------------------------------------------------------------------- /__mocks__/sieve.js: -------------------------------------------------------------------------------- 1 | // __mocks__/styleMock.js 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /components/option/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Option } from './Option'; 2 | -------------------------------------------------------------------------------- /components/select/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Select } from './Select'; 2 | -------------------------------------------------------------------------------- /components/timezoneSelector/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TimezoneSelector'; 2 | -------------------------------------------------------------------------------- /components/toggle/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Toggle } from './Toggle'; 2 | -------------------------------------------------------------------------------- /components/wizard/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Wizards } from './Wizard'; 2 | -------------------------------------------------------------------------------- /containers/calendar/exportModal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ExportModal'; 2 | -------------------------------------------------------------------------------- /containers/keys/calendar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './setupCalendarKeys'; 2 | -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | // __mocks__/styleMock.js 2 | 3 | module.exports = {}; 4 | -------------------------------------------------------------------------------- /components/fileIcon/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FileIcon } from './FileIcon'; 2 | -------------------------------------------------------------------------------- /components/stepDot/index.ts: -------------------------------------------------------------------------------- 1 | export { default as StepDot } from './StepDot'; 2 | -------------------------------------------------------------------------------- /components/stepDots/index.ts: -------------------------------------------------------------------------------- 1 | export { default as StepDots } from './StepDots'; 2 | -------------------------------------------------------------------------------- /components/tooltip/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Tooltip } from './Tooltip'; 2 | -------------------------------------------------------------------------------- /containers/calendar/calendarModal/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CalendarModal'; 2 | -------------------------------------------------------------------------------- /helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component'; 2 | export * from './appVersion'; 3 | -------------------------------------------------------------------------------- /hooks/helpers/vendorEncoder.ts: -------------------------------------------------------------------------------- 1 | export { default as punycode } from 'punycode'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "proton-shared/tsconfig.base.json" 3 | } 4 | -------------------------------------------------------------------------------- /components/breadcrumb/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Breadcrumb } from './Breadcrumb'; 2 | -------------------------------------------------------------------------------- /components/countdown/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Countdown } from './Countdown'; 2 | -------------------------------------------------------------------------------- /components/focus/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useFocusTrap } from './useFocusTrap'; 2 | -------------------------------------------------------------------------------- /components/labelStack/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LabelStack } from './LabelStack'; 2 | -------------------------------------------------------------------------------- /components/paragraph/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Paragraph } from './Paragraph'; 2 | -------------------------------------------------------------------------------- /containers/cache/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CacheProvider } from './Provider'; 2 | -------------------------------------------------------------------------------- /components/color/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ColorSelector } from './ColorSelector'; 2 | -------------------------------------------------------------------------------- /components/contextMenu/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ContextMenu } from './ContextMenu'; 2 | -------------------------------------------------------------------------------- /components/scroll/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ScrollShadows } from './ScrollShadows'; 2 | -------------------------------------------------------------------------------- /components/tabs/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Tabs, default as SimpleTabs } from './Tabs'; 2 | -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | // __mocks__/fileMock.js 2 | 3 | module.exports = 'test-file-stub'; 4 | -------------------------------------------------------------------------------- /components/attachedFile/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AttachedFile } from './AttachedFile'; 2 | -------------------------------------------------------------------------------- /components/editableText/index.ts: -------------------------------------------------------------------------------- 1 | export { default as EditableText } from './EditableText'; 2 | -------------------------------------------------------------------------------- /components/ellipsis/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MiddleEllipsis } from './MiddleEllipsis'; 2 | -------------------------------------------------------------------------------- /components/truncatedText/index.ts: -------------------------------------------------------------------------------- 1 | export { default as TruncatedText } from './TruncatedText'; 2 | -------------------------------------------------------------------------------- /containers/importAssistant/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mail'; 2 | export * from './calendar'; 3 | -------------------------------------------------------------------------------- /containers/mail/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MailShortcutsModal } from './MailShortcutsModal'; 2 | -------------------------------------------------------------------------------- /components/fileNameDisplay/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FileNameDisplay } from './FileNameDisplay'; 2 | -------------------------------------------------------------------------------- /containers/autoReply/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AutoReplySection } from './AutoReplySection'; 2 | -------------------------------------------------------------------------------- /containers/calendar/importModal/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ImportModal } from './ImportModal'; 2 | -------------------------------------------------------------------------------- /containers/earlyAccess/index.ts: -------------------------------------------------------------------------------- 1 | export { default as EarlyAccessModal } from './EarlyAccessModal'; 2 | -------------------------------------------------------------------------------- /containers/calendar/notifications/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Notifications } from './Notifications'; 2 | -------------------------------------------------------------------------------- /containers/filters/modal/advanced/SieveEditor.scss: -------------------------------------------------------------------------------- 1 | .CodeMirror-lint-tooltip { 2 | z-index: 1000; 3 | } 4 | -------------------------------------------------------------------------------- /components/dragMoveContainer/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DragMoveContainer } from './DragMoveContainer'; 2 | -------------------------------------------------------------------------------- /components/dropzone/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Dropzone } from './Dropzone'; 2 | export * from './helpers'; 3 | -------------------------------------------------------------------------------- /containers/api/apiContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export default createContext(); 4 | -------------------------------------------------------------------------------- /containers/bridge/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ProtonMailBridgeSection } from './ProtonMailBridgeSection'; 2 | -------------------------------------------------------------------------------- /containers/illustration/index.ts: -------------------------------------------------------------------------------- 1 | export { default as IllustrationPlaceholder } from './IllustrationPlaceholder'; 2 | -------------------------------------------------------------------------------- /containers/modals/modalsContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export default createContext(); 4 | -------------------------------------------------------------------------------- /components/collapsingBreadcrumbs/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CollapsingBreadcrumbs } from './CollapsingBreadcrumbs'; 2 | -------------------------------------------------------------------------------- /containers/modals/childrenContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export default createContext(); 4 | -------------------------------------------------------------------------------- /components/selectTwo/select.ts: -------------------------------------------------------------------------------- 1 | export type SelectChangeEvent = { 2 | value: V; 3 | selectedIndex: number; 4 | }; 5 | -------------------------------------------------------------------------------- /components/title/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SubTitle } from './SubTitle'; 2 | export { default as Title } from './Title'; 3 | -------------------------------------------------------------------------------- /containers/importAssistant/calendar/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ImportCalendarModal } from './modals/ImportCalendarModal'; 2 | -------------------------------------------------------------------------------- /containers/importAssistant/contacts/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ImportContactsModal } from './modals/ImportContactsModal'; 2 | -------------------------------------------------------------------------------- /components/input/LayoutCard.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .layout-card-image { 4 | width: rem(149); 5 | } 6 | -------------------------------------------------------------------------------- /containers/api/apiServerTimeContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export default createContext(undefined); 4 | -------------------------------------------------------------------------------- /containers/contacts/modals/ContactGroupDetailsModal.scss: -------------------------------------------------------------------------------- 1 | .contact-group-details-chip { 2 | height: 1em; 3 | width: 1em; 4 | } 5 | -------------------------------------------------------------------------------- /containers/rightToLeft/context.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export default createContext({ isRTL: false }); 4 | -------------------------------------------------------------------------------- /containers/themes/ThemeCard.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .theme-card-image { 4 | width: rem(122); 5 | } 6 | -------------------------------------------------------------------------------- /components/image/index.ts: -------------------------------------------------------------------------------- 1 | export { default as RemoteImage } from './RemoteImage'; 2 | export { default as QRCode } from './QRCode'; 3 | -------------------------------------------------------------------------------- /containers/contacts/ContactProviderContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export default createContext(new Map()); 4 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | include: 2 | - project: 'deploy-app/fe-scripts' 3 | ref: master 4 | file: '/jobs/lib/libs.gitlab-ci.yaml' 5 | 6 | -------------------------------------------------------------------------------- /components/miniCalendar/index.d.ts: -------------------------------------------------------------------------------- 1 | export type WeekStartsOn = 0 | 1 | 2 | 3 | 4 | 5 | 6; 2 | 3 | export type DateTuple = [Date, Date]; 4 | -------------------------------------------------------------------------------- /containers/filters/spamlist/SpamListItem.scss: -------------------------------------------------------------------------------- 1 | .SpamListItem-list:not(:empty) { 2 | max-height: 300px; // better not use rem there 3 | } 4 | -------------------------------------------------------------------------------- /components/shortcuts/ShortcutsModal.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .shortcut-modal kbd { 4 | word-spacing: rem(-3); 5 | } 6 | -------------------------------------------------------------------------------- /components/addressesAutomplete/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AddressesAutocomplete } from './AddressesAutocomplete'; 2 | export * from './helper'; 3 | -------------------------------------------------------------------------------- /components/toolbar/ToolbarSeparator.tsx: -------------------------------------------------------------------------------- 1 | import Vr from '../vr/Vr'; 2 | 3 | const ToolbarSeparator = Vr; 4 | 5 | export default ToolbarSeparator; 6 | -------------------------------------------------------------------------------- /components/version/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AppVersion } from './AppVersion'; 2 | export { default as ChangelogModal } from './ChangelogModal'; 3 | -------------------------------------------------------------------------------- /containers/account/SettingsLayoutRight.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .settings-layout-right { 4 | min-width: em(240); 5 | } 6 | -------------------------------------------------------------------------------- /components/tabs/index.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export interface Tab { 4 | title: string; 5 | content?: React.ReactNode; 6 | } 7 | -------------------------------------------------------------------------------- /containers/password/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AuthModal } from './AuthModal'; 2 | export { default as PasswordTotpInputs } from './PasswordTotpInputs'; 3 | -------------------------------------------------------------------------------- /helpers/appVersion.ts: -------------------------------------------------------------------------------- 1 | export const getAppVersion = (versionString: string) => { 2 | return versionString.replace(/-beta.(\d+)/, ' - Beta $1'); 3 | }; 4 | -------------------------------------------------------------------------------- /components/alert/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Alert } from './Alert'; 2 | export { default as DoNotWindowOpenAlertError } from './DoNotWindowOpenAlertError'; 3 | -------------------------------------------------------------------------------- /components/treeview/index.ts: -------------------------------------------------------------------------------- 1 | export { default as TreeViewContainer } from './TreeViewContainer'; 2 | export { default as TreeViewItem } from './TreeViewItem'; 3 | -------------------------------------------------------------------------------- /containers/rightToLeft/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useRightToLeft } from './useRightToLeft'; 2 | export { default as RightToLeftProvider } from './Provider'; 3 | -------------------------------------------------------------------------------- /containers/sessions/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SessionsSection } from './SessionsSection'; 2 | export { default as SessionAction } from './SessionAction'; 3 | -------------------------------------------------------------------------------- /containers/support/index.ts: -------------------------------------------------------------------------------- 1 | export { default as BugModal } from './BugModal'; 2 | export { default as AuthenticatedBugModal } from './AuthenticatedBugModal'; 3 | -------------------------------------------------------------------------------- /components/orderable/OrderableElement.js: -------------------------------------------------------------------------------- 1 | import { SortableElement } from 'react-sortable-hoc'; 2 | 3 | export default SortableElement(({ children }) => children); 4 | -------------------------------------------------------------------------------- /components/orderable/OrderableHandle.js: -------------------------------------------------------------------------------- 1 | import { SortableHandle } from 'react-sortable-hoc'; 2 | 3 | export default SortableHandle(({ children }) => children); 4 | -------------------------------------------------------------------------------- /components/orderable/OrderableContainer.js: -------------------------------------------------------------------------------- 1 | import { SortableContainer } from 'react-sortable-hoc'; 2 | 3 | export default SortableContainer(({ children }) => children); 4 | -------------------------------------------------------------------------------- /components/shortcuts/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ShortcutsModal } from './ShortcutsModal'; 2 | export { default as ShortcutsSectionView } from './ShortcutsSectionView'; 3 | -------------------------------------------------------------------------------- /containers/notifications/index.ts: -------------------------------------------------------------------------------- 1 | export { default as NotificationsContainer } from './Container'; 2 | export { default as NotificationsProvider } from './Provider'; 3 | -------------------------------------------------------------------------------- /components/miniCalendar/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MiniCalendar } from './MiniCalendar'; 2 | export { default as LocalizedMiniCalendar } from './LocalizedMiniCalendar'; 3 | -------------------------------------------------------------------------------- /components/orderableTable/OrderableTableHeader.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .orderableTableHeader { 4 | th:first-child { 5 | width: rem(35); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /components/selectTwo/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SelectTwo } from './SelectTwo'; 2 | export { default as SearchableSelect } from './SearchableSelect'; 3 | export * from './SelectTwo'; 4 | -------------------------------------------------------------------------------- /components/editor/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SquireEditor } from './SquireEditor'; 2 | export { default as SimpleSquireEditor } from './SimpleSquireEditor'; 3 | export * from './interface'; 4 | -------------------------------------------------------------------------------- /components/popper/index.ts: -------------------------------------------------------------------------------- 1 | export { default as usePopper } from './usePopper'; 2 | export { default as usePopperAnchor } from './usePopperAnchor'; 3 | export { default as Popper } from './Popper'; 4 | -------------------------------------------------------------------------------- /containers/keys/shared/helper.ts: -------------------------------------------------------------------------------- 1 | export const getKeyByID = (keys: T[], ID: string): T | undefined => { 2 | return keys.find(({ ID: otherID }) => otherID === ID); 3 | }; 4 | -------------------------------------------------------------------------------- /containers/organization/index.ts: -------------------------------------------------------------------------------- 1 | export { default as OrganizationSection } from './OrganizationSection'; 2 | export { default as OrganizationPasswordSection } from './OrganizationPasswordSection'; 3 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import './typings/index.d'; 2 | import './typings/css.d'; 3 | 4 | export * from './hooks'; 5 | export * from './helpers'; 6 | export * from './components'; 7 | export * from './containers'; 8 | -------------------------------------------------------------------------------- /containers/cache/cacheContext.ts: -------------------------------------------------------------------------------- 1 | import { Cache } from 'proton-shared/lib/helpers/cache'; 2 | import { createContext } from 'react'; 3 | 4 | export default createContext | null>(null); 5 | -------------------------------------------------------------------------------- /containers/features/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FeaturesProvider } from './FeaturesProvider'; 2 | export { default as FeaturesContext } from './FeaturesContext'; 3 | export * from './FeaturesContext'; 4 | -------------------------------------------------------------------------------- /containers/logs/index.ts: -------------------------------------------------------------------------------- 1 | export { default as WipeLogsButton } from './WipeLogsButton'; 2 | export { default as LogsSection } from './LogsSection'; 3 | export { default as LogsTable } from './LogsTable'; 4 | -------------------------------------------------------------------------------- /containers/modals/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ModalsContainer } from './Container'; 2 | export { default as ModalsChildren } from './Children'; 3 | export { default as ModalsProvider } from './Provider'; 4 | -------------------------------------------------------------------------------- /containers/notification/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DesktopNotificationSection } from './DesktopNotificationSection'; 2 | export { default as RecoveryMethodsSection } from './RecoveryMethodsSection'; 3 | -------------------------------------------------------------------------------- /components/title/Title.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Title = ({ children, ...rest }: React.HTMLAttributes) =>

{children}

; 4 | 5 | export default Title; 6 | -------------------------------------------------------------------------------- /components/toolbar/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ToolbarSeparator } from './ToolbarSeparator'; 2 | export { default as Toolbar } from './Toolbar'; 3 | export { default as ToolbarButton } from './ToolbarButton'; 4 | -------------------------------------------------------------------------------- /containers/payments/subscription/constants.ts: -------------------------------------------------------------------------------- 1 | export enum SUBSCRIPTION_STEPS { 2 | NETWORK_ERROR, 3 | PLAN_SELECTION, 4 | CUSTOMIZATION, 5 | CHECKOUT, 6 | UPGRADE, 7 | THANKS, 8 | } 9 | -------------------------------------------------------------------------------- /containers/search/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SearchSection } from './SearchSection'; 2 | export { default as Searchbox } from './Searchbox'; 3 | export { default as SearchDropdown } from './SearchDropdown'; 4 | -------------------------------------------------------------------------------- /hooks/useFilters.js: -------------------------------------------------------------------------------- 1 | import { FiltersModel } from 'proton-shared/lib/models/filtersModel'; 2 | import createUseModelHook from './helpers/createModelHook'; 3 | 4 | export default createUseModelHook(FiltersModel); 5 | -------------------------------------------------------------------------------- /containers/overview/index.ts: -------------------------------------------------------------------------------- 1 | export { default as IndexSection } from './IndexSection'; 2 | export { default as SummarySection } from './SummarySection'; 3 | export { default as OverviewLayout } from './OverviewLayout'; 4 | -------------------------------------------------------------------------------- /containers/rightToLeft/useRightToLeft.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import Context from './context'; 4 | 5 | const useRightToLeft = () => useContext(Context); 6 | 7 | export default useRightToLeft; 8 | -------------------------------------------------------------------------------- /hooks/useContacts.js: -------------------------------------------------------------------------------- 1 | import { ContactsModel } from 'proton-shared/lib/models/contactsModel'; 2 | import createUseModelHook from './helpers/createModelHook'; 3 | 4 | export default createUseModelHook(ContactsModel); 5 | -------------------------------------------------------------------------------- /typings/css.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'csstype' { 2 | // eslint-disable-next-line no-unused-vars 3 | interface Properties { 4 | // allow css variables 5 | [index: string]: unknown; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /components/editor/toolbar/SquireToolbarSeparator.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const SquireToolbarSeparator = () => ; 4 | 5 | export default SquireToolbarSeparator; 6 | -------------------------------------------------------------------------------- /containers/bridge/ProtonMailBridgeSection.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .bridge-grid { 4 | display: grid; 5 | grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); 6 | grid-gap: 1em; 7 | } 8 | -------------------------------------------------------------------------------- /containers/config/configContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { ProtonConfig } from 'proton-shared/lib/interfaces'; 3 | 4 | export default createContext(null as unknown as ProtonConfig); 5 | -------------------------------------------------------------------------------- /containers/messages/index.ts: -------------------------------------------------------------------------------- 1 | export { default as RemoteToggle } from './RemoteToggle'; 2 | export { default as EmbeddedToggle } from './EmbeddedToggle'; 3 | export { default as MessagesSection } from './MessagesSection'; 4 | -------------------------------------------------------------------------------- /components/input/TelInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Input from './Input'; 4 | 5 | const TelInput = (props) => { 6 | return ; 7 | }; 8 | 9 | export default TelInput; 10 | -------------------------------------------------------------------------------- /components/text/Legend.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Legend = ({ children, ...rest }: React.HTMLAttributes) => ( 4 | {children} 5 | ); 6 | 7 | export default Legend; 8 | -------------------------------------------------------------------------------- /components/globalLoader/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as GlobalLoader } from './GlobalLoader'; 2 | export { default as GlobalLoaderProvider } from './GlobalLoaderProvider'; 3 | export { default as useGlobalLoader } from './useGlobalLoader'; 4 | -------------------------------------------------------------------------------- /components/orderable/index.js: -------------------------------------------------------------------------------- 1 | export { default as OrderableContainer } from './OrderableContainer'; 2 | export { default as OrderableElement } from './OrderableElement'; 3 | export { default as OrderableHandle } from './OrderableHandle'; 4 | -------------------------------------------------------------------------------- /containers/compatibilityCheck/index.ts: -------------------------------------------------------------------------------- 1 | export { default as CompatibilityCheck } from './CompatibilityCheck'; 2 | export { default as CompatibilityCheckView } from './CompatibilityCheckView'; 3 | export * from './compatibilityCheckHelper'; 4 | -------------------------------------------------------------------------------- /containers/forceRefresh/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { noop } from 'proton-shared/lib/helpers/function'; 3 | 4 | export type RefreshFn = () => void; 5 | 6 | export default createContext(noop); 7 | -------------------------------------------------------------------------------- /containers/importAssistant/interfaces.ts: -------------------------------------------------------------------------------- 1 | export enum OAUTH_PROVIDER { 2 | GOOGLE = 1, 3 | } 4 | 5 | export interface OAuthProps { 6 | code: string; 7 | provider: OAUTH_PROVIDER; 8 | redirectURI: string; 9 | } 10 | -------------------------------------------------------------------------------- /containers/keys/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AddressKeysSection } from './AddressKeysSection'; 2 | export { default as UserKeysSection } from './UserKeysSection'; 3 | export { default as SelectKeyFiles } from './shared/SelectKeyFiles'; 4 | -------------------------------------------------------------------------------- /hooks/useContactEmails.js: -------------------------------------------------------------------------------- 1 | import { ContactEmailsModel } from 'proton-shared/lib/models/contactEmailsModel'; 2 | import createUseModelHook from './helpers/createModelHook'; 3 | 4 | export default createUseModelHook(ContactEmailsModel); 5 | -------------------------------------------------------------------------------- /hooks/useMessageCounts.js: -------------------------------------------------------------------------------- 1 | import { MessageCountsModel } from 'proton-shared/lib/models/messageCountsModel'; 2 | import createUseModelHook from './helpers/createModelHook'; 3 | 4 | export default createUseModelHook(MessageCountsModel); 5 | -------------------------------------------------------------------------------- /hooks/usePaymentMethods.js: -------------------------------------------------------------------------------- 1 | import { PaymentMethodsModel } from 'proton-shared/lib/models/paymentMethodsModel'; 2 | import createUseModelHook from './helpers/createModelHook'; 3 | 4 | export default createUseModelHook(PaymentMethodsModel); 5 | -------------------------------------------------------------------------------- /components/contacts/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ContactGroupTable } from './ContactGroupTable'; 2 | export { default as ContactGroupIcon } from './ContactGroupIcon'; 3 | export { default as ContactUpgradeModal } from './ContactUpgradeModal'; 4 | -------------------------------------------------------------------------------- /containers/notifications/notificationsContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import createNotificationManager from './manager'; 3 | 4 | export default createContext | null>(null); 5 | -------------------------------------------------------------------------------- /hooks/useModals.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import ModalsContext from '../containers/modals/modalsContext'; 3 | 4 | const useModals = () => { 5 | return useContext(ModalsContext); 6 | }; 7 | 8 | export default useModals; 9 | -------------------------------------------------------------------------------- /containers/eventManager/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import createEventManager from 'proton-shared/lib/eventManager/eventManager'; 3 | 4 | export default createContext | null>(null); 5 | -------------------------------------------------------------------------------- /containers/api/humanVerification/interface.ts: -------------------------------------------------------------------------------- 1 | export type VerificationModel = 2 | | { 3 | method: 'sms'; 4 | value: string; 5 | } 6 | | { 7 | method: 'email'; 8 | value: string; 9 | }; 10 | -------------------------------------------------------------------------------- /containers/calendar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './notifications'; 2 | export * from './settings'; 3 | export { default as CalendarInviteButtons } from './CalendarInviteButtons'; 4 | export { default as CalendarShareSection } from './shareURL/ShareSection'; 5 | -------------------------------------------------------------------------------- /hooks/useApiStatus.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import ContextApi from '../containers/api/apiStatusContext'; 4 | 5 | const useApiStatus = () => { 6 | return useContext(ContextApi); 7 | }; 8 | 9 | export default useApiStatus; 10 | -------------------------------------------------------------------------------- /hooks/useConfig.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import ConfigContext from '../containers/config/configContext'; 4 | 5 | const useConfig = () => { 6 | return useContext(ConfigContext); 7 | }; 8 | 9 | export default useConfig; 10 | -------------------------------------------------------------------------------- /hooks/useConversationCounts.js: -------------------------------------------------------------------------------- 1 | import { ConversationCountsModel } from 'proton-shared/lib/models/conversationCountsModel'; 2 | import createUseModelHook from './helpers/createModelHook'; 3 | 4 | export default createUseModelHook(ConversationCountsModel); 5 | -------------------------------------------------------------------------------- /hooks/useForceRefresh.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import Context from '../containers/forceRefresh/context'; 4 | 5 | const useForceRefresh = () => { 6 | return useContext(Context); 7 | }; 8 | 9 | export default useForceRefresh; 10 | -------------------------------------------------------------------------------- /components/title/SubTitle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export type SubTitleProps = React.HTMLAttributes; 4 | 5 | const SubTitle = ({ children, ...rest }: SubTitleProps) =>

{children}

; 6 | 7 | export default SubTitle; 8 | -------------------------------------------------------------------------------- /containers/api/apiStatusContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | export const defaultApiStatus = { 4 | offline: false, 5 | apiUnreachable: '', 6 | appVersionBad: false, 7 | }; 8 | 9 | export default createContext(defaultApiStatus); 10 | -------------------------------------------------------------------------------- /containers/importAssistant/mail/index.ts: -------------------------------------------------------------------------------- 1 | export { default as StartMailImportSection } from './StartImportSection'; 2 | export { default as MailImportExportSection } from './ImportExportSection'; 3 | export { default as MailImportListSection } from './ImportListSection'; 4 | -------------------------------------------------------------------------------- /containers/onboarding/OnboardingStep.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { OnboardingStepProps } from './interface'; 3 | 4 | const OnboardingStep = ({ children }: OnboardingStepProps) => { 5 | return <>{children}; 6 | } 7 | 8 | export default OnboardingStep; 9 | -------------------------------------------------------------------------------- /containers/security/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ExternalPGPSettingsSection } from './ExternalPGPSettingsSection'; 2 | export { default as AddressVerificationSection } from './AddressVerificationSection'; 3 | export { default as PGPSchemeSelect } from './PGPSchemeSelect'; 4 | -------------------------------------------------------------------------------- /containers/labels/index.ts: -------------------------------------------------------------------------------- 1 | export { default as LabelsSection } from './LabelsSection'; 2 | export { default as FoldersSection } from './FoldersSection'; 3 | export { default as LabelModal } from './modals/EditLabelModal'; 4 | export { default as FolderIcon } from './FolderIcon'; 5 | -------------------------------------------------------------------------------- /hooks/useApiServerTime.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | 3 | import ContextApi from '../containers/api/apiServerTimeContext'; 4 | 5 | const useApiServerTime = () => { 6 | return useContext(ContextApi); 7 | }; 8 | 9 | export default useApiServerTime; 10 | -------------------------------------------------------------------------------- /hooks/useApps.ts: -------------------------------------------------------------------------------- 1 | import { APPS } from 'proton-shared/lib/constants'; 2 | 3 | const { PROTONMAIL, PROTONCALENDAR, PROTONDRIVE } = APPS; 4 | 5 | const useApps = () => { 6 | return [PROTONMAIL, PROTONCALENDAR, PROTONDRIVE]; 7 | }; 8 | 9 | export default useApps; 10 | -------------------------------------------------------------------------------- /components/text/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Legend } from './Legend'; 2 | export { default as ErrorZone } from './ErrorZone'; 3 | export { default as Mark } from './Mark'; 4 | export { default as Marks } from './Marks'; 5 | export { default as Preformatted } from './Preformatted'; 6 | -------------------------------------------------------------------------------- /hooks/useDomains.ts: -------------------------------------------------------------------------------- 1 | import { DomainsModel } from 'proton-shared/lib/models/domainsModel'; 2 | import { Domain } from 'proton-shared/lib/interfaces'; 3 | import createUseModelHook from './helpers/createModelHook'; 4 | 5 | export default createUseModelHook(DomainsModel); 6 | -------------------------------------------------------------------------------- /components/loader/LoaderIcon.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import CircleLoader from './CircleLoader'; 3 | 4 | const LoaderIcon = () => ( 5 |
6 | 7 |
8 | ); 9 | 10 | export default LoaderIcon; 11 | -------------------------------------------------------------------------------- /components/pagination/index.ts: -------------------------------------------------------------------------------- 1 | export { default as usePaginationAsync } from './usePaginationAsync'; 2 | export { default as usePagination } from './usePagination'; 3 | export { default as Pagination } from './Pagination'; 4 | export { default as PaginationRow } from './PaginationRow'; 5 | -------------------------------------------------------------------------------- /containers/account/SettingsLayout.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .settings-layout { 4 | display: flex; 5 | flex-wrap: nowrap; 6 | margin-bottom: 1em; 7 | 8 | @include respond-to($breakpoint-small, 'max') { 9 | flex-direction: column; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /components/icon/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Icon } from './Icon'; 2 | export { default as RoundedIcon } from './RoundedIcon'; 3 | export { default as Icons } from './Icons'; 4 | export { default as MimeIcon } from './MimeIcon'; 5 | export { default as KeyWarningIcon } from './KeyWarningIcon'; 6 | -------------------------------------------------------------------------------- /components/progress/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Progress } from './Progress'; 2 | export { default as DynamicProgress } from './DynamicProgress'; 3 | export * from './Meter'; 4 | export { default as Meter } from './Meter'; 5 | export { default as CircularProgress } from './CircularProgress'; 6 | -------------------------------------------------------------------------------- /rtl.setup.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/react/cleanup-after-each'; 2 | import '@testing-library/jest-dom/extend-expect'; 3 | 4 | // Silence warnings on expect to throw https://github.com/testing-library/react-testing-library/issues/157 5 | console.error = () => {}; 6 | console.warn = () => {}; 7 | -------------------------------------------------------------------------------- /components/contacts/ContactSummary.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | // ????? 4 | .contactsummary-list-item { 5 | height: rem(25); 6 | } 7 | 8 | @include respond-to($breakpoint-small) { 9 | .contact-modal-select { 10 | max-width: calc(100% - 3.3em); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /containers/api/humanVerification/HumanVerificationModal.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .modal--human-verification { 4 | &#{&} { 5 | // Specificity -_-v 6 | // Just tested this value, don't change it 7 | width: rem(730); 8 | max-width: rem(730); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /containers/error/index.ts: -------------------------------------------------------------------------------- 1 | export { default as GenericError } from './GenericError'; 2 | export { default as InternalServerError } from './InternalServerError'; 3 | export { default as NotFoundError } from './NotFoundError'; 4 | export { default as AccessDeniedError } from './AccessDeniedError'; 5 | -------------------------------------------------------------------------------- /containers/items/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useItemsSelection } from './useItemsSelection'; 2 | export { default as useItemsDraggable } from './useItemsDraggable'; 3 | export { default as useItemsDroppable } from './useItemsDroppable'; 4 | export { default as ItemCheckbox } from './ItemCheckbox'; 5 | -------------------------------------------------------------------------------- /hooks/useApi.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { Api } from 'proton-shared/lib/interfaces'; 3 | 4 | import ContextApi from '../containers/api/apiContext'; 5 | 6 | const useApi = (): Api => { 7 | return useContext(ContextApi); 8 | }; 9 | 10 | export default useApi; 11 | -------------------------------------------------------------------------------- /hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const usePrevious = (value: T) => { 4 | const ref = useRef(); 5 | useEffect(() => { 6 | ref.current = value; 7 | }); 8 | return ref.current; 9 | }; 10 | 11 | export default usePrevious; 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "arrowParens": "always", 4 | "singleQuote": true, 5 | "tabWidth": 4, 6 | "proseWrap": "never", 7 | "overrides": [ 8 | { 9 | "files": "*.scss", 10 | "options": { 11 | "useTabs": true 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'], 3 | plugins: [ 4 | '@babel/plugin-proposal-object-rest-spread', 5 | '@babel/plugin-transform-runtime', 6 | 'transform-require-context' 7 | ] 8 | }; 9 | -------------------------------------------------------------------------------- /containers/importAssistant/mail/modals/ImportMailModal.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .modal.import-modal { 4 | width: 100%; 5 | max-width: rem(1200); 6 | max-height: 90%; 7 | } 8 | 9 | .modal.customize-import-modal { 10 | max-width: rem(840); 11 | width: 100%; 12 | } 13 | -------------------------------------------------------------------------------- /components/challenge/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Challenge } from './Challenge'; 2 | export { default as ChallengeError } from './ChallengeError'; 3 | export { default as ChallengeFrame } from './ChallengeFrame'; 4 | export * from './interface'; 5 | export { captureChallengeMessage } from './challengeHelper'; 6 | -------------------------------------------------------------------------------- /containers/themes/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ThemeInjector } from './ThemeInjector'; 2 | export { default as ThemesSection } from './ThemesSection'; 3 | export { default as ThemeCard } from './ThemeCard'; 4 | export { default as ThemeCards } from './ThemeCards'; 5 | export { useTheme } from './ThemeProvider'; 6 | -------------------------------------------------------------------------------- /components/orderableTable/index.ts: -------------------------------------------------------------------------------- 1 | export { default as OrderableTable } from './OrderableTable'; 2 | export { default as OrderableTableHeader } from './OrderableTableHeader'; 3 | export { default as OrderableTableBody } from './OrderableTableBody'; 4 | export { default as OrderableTableRow } from './OrderableTableRow'; 5 | -------------------------------------------------------------------------------- /hooks/useUserSettings.ts: -------------------------------------------------------------------------------- 1 | import { UserSettingsModel } from 'proton-shared/lib/models/userSettingsModel'; 2 | import { UserSettings } from 'proton-shared/lib/interfaces/UserSettings'; 3 | import createUseModelHook from './helpers/createModelHook'; 4 | 5 | export default createUseModelHook(UserSettingsModel); 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_size = 4 6 | indent_style = space 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.scss] 16 | indent_style = tab -------------------------------------------------------------------------------- /components/portal/Portal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | interface Props { 5 | children: React.ReactNode; 6 | } 7 | 8 | const Portal = ({ children }: Props) => { 9 | return ReactDOM.createPortal(children, document.body); 10 | }; 11 | 12 | export default Portal; 13 | -------------------------------------------------------------------------------- /containers/authentication/authenticationContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | import { PrivateAuthenticationStore, PublicAuthenticationStore } from '../app/interface'; 3 | 4 | // Trusting this always gets set 5 | export default createContext(null as any); 6 | -------------------------------------------------------------------------------- /containers/payments/CredisModal.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .credits-modal .field-container.payment-right { 4 | width: 70%; 5 | } 6 | 7 | @include respond-to($breakpoint-small) { 8 | .credits-modal .label.payment-left, 9 | .credits-modal .field-container.payment-right { 10 | width: 100%; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /containers/payments/DonateModal.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .donate-modal .field-container.payment-right { 4 | width: 70%; 5 | } 6 | 7 | @include respond-to($breakpoint-small) { 8 | .donate-modal .label.payment-left, 9 | .donate-modal .field-container.payment-right { 10 | width: 100%; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /components/v2/index.ts: -------------------------------------------------------------------------------- 1 | export { default as InputTwo } from './input/Input'; 2 | export { default as PhoneInput } from './phone/LazyPhoneInput'; 3 | export { default as PasswordInputTwo } from './input/PasswordInput'; 4 | export { default as InputFieldTwo } from './field/InputField'; 5 | export { default as useFormErrors } from './useFormErrors'; 6 | -------------------------------------------------------------------------------- /containers/account/SettingsSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | const SettingsSection = ({ className, ...rest }: React.ComponentPropsWithoutRef<'div'>) => { 5 | return
; 6 | }; 7 | 8 | export default SettingsSection; 9 | -------------------------------------------------------------------------------- /containers/autoReply/AutoReplyForm/AutoReplyFormPermanent.tsx: -------------------------------------------------------------------------------- 1 | import { c } from 'ttag'; 2 | import React from 'react'; 3 | 4 | import Alert from '../../../components/alert/Alert'; 5 | 6 | const AutoReplyFormPermanent = () => {c('Info').t`Auto-reply is active until you turn it off.`}; 7 | 8 | export default AutoReplyFormPermanent; 9 | -------------------------------------------------------------------------------- /containers/items/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * ID used in the drag DataTransfer object to store items ids currently dragged 3 | */ 4 | export const DRAG_ITEM_KEY = 'drag-item'; 5 | 6 | /** 7 | * ID used in the drag DataTransfer object to store the HTML id of the dragged element 8 | */ 9 | export const DRAG_ITEM_ID_KEY = 'drag-item-id'; 10 | -------------------------------------------------------------------------------- /containers/onboarding/index.ts: -------------------------------------------------------------------------------- 1 | export { default as OnboardingContent } from './OnboardingContent'; 2 | export { default as OnboardingModal } from './OnboardingModal'; 3 | export { default as OnboardingStep } from './OnboardingStep'; 4 | export { default as BetaOnboardingModal } from './BetaOnboardingModal'; 5 | 6 | export * from './interface'; 7 | -------------------------------------------------------------------------------- /components/autocomplete/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useAutocomplete'; 2 | export { default as AutocompleteSuggestions } from './AutocompleteList'; 3 | export { default as Autocomplete } from './Autocomplete'; 4 | export { default as SimpleAutocomplete } from './SimpleAutocomplete'; 5 | export { default as AutocompleteList } from './AutocompleteList'; 6 | -------------------------------------------------------------------------------- /containers/contacts/modals/ContactListModal.scss: -------------------------------------------------------------------------------- 1 | .contact-list { 2 | .ReactVirtualized__Grid__innerScrollContainer { 3 | border-right: 0; 4 | } 5 | } 6 | 7 | .contact-list-row { 8 | user-select: none; 9 | border-bottom: 1px solid var(--border-norm); 10 | 11 | &--selected { 12 | background: var(--background-strong); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /hooks/useInstance.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | /** 4 | * Use ref with callback support. 5 | */ 6 | const useInstance = (fn: () => T): T => { 7 | const ref = useRef(); 8 | if (!ref.current) { 9 | ref.current = fn(); 10 | } 11 | return ref.current; 12 | }; 13 | 14 | export default useInstance; 15 | -------------------------------------------------------------------------------- /hooks/useLoginType.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { LoginTypes } from 'proton-shared/lib/authentication/LoginInterface'; 3 | 4 | const useLoginType = () => { 5 | const [loginType] = useState(() => { 6 | return LoginTypes.PERSISTENT; 7 | }); 8 | return loginType; 9 | }; 10 | 11 | export default useLoginType; 12 | -------------------------------------------------------------------------------- /components/dragMoveContainer/DragMoveContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface Props { 4 | children: React.ReactNode; 5 | } 6 | 7 | const DragMoveContainer = ({ children }: Props) => ( 8 |
{children}
9 | ); 10 | 11 | export default DragMoveContainer; 12 | -------------------------------------------------------------------------------- /components/text/Preformatted.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | const Preformatted = ({ className = '', ...rest }: React.HTMLAttributes) => { 5 | return
;
6 | };
7 | 
8 | export default Preformatted;
9 | 


--------------------------------------------------------------------------------
/containers/app/useOnLogout.ts:
--------------------------------------------------------------------------------
 1 | import { useEffect } from 'react';
 2 | import { useAuthentication } from '../../hooks';
 3 | 
 4 | const useOnLogout = (cb: () => Promise) => {
 5 |     const { onLogout } = useAuthentication();
 6 |     useEffect(() => {
 7 |         return onLogout(cb);
 8 |     }, [cb]);
 9 | };
10 | 
11 | export default useOnLogout;
12 | 


--------------------------------------------------------------------------------
/components/logo/MainLogo.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react';
 2 | import { useConfig } from '../../hooks';
 3 | 
 4 | import Logo, { LogoProps } from './Logo';
 5 | 
 6 | const MainLogo = (props: Omit) => {
 7 |     const { APP_NAME } = useConfig();
 8 | 
 9 |     return ;
10 | };
11 | 
12 | export default MainLogo;
13 | 


--------------------------------------------------------------------------------
/containers/account/SettingsSectionWide.tsx:
--------------------------------------------------------------------------------
 1 | import React from 'react';
 2 | 
 3 | import { classnames } from '../../helpers';
 4 | 
 5 | const SettingsSectionWide = ({ className, ...rest }: React.ComponentPropsWithoutRef<'div'>) => {
 6 |     return 
; 7 | }; 8 | 9 | export default SettingsSectionWide; 10 | -------------------------------------------------------------------------------- /containers/keys/importKeys/interface.ts: -------------------------------------------------------------------------------- 1 | import { OpenPGPKey } from 'pmcrypto'; 2 | 3 | export enum Status { 4 | SUCCESS = 1, 5 | LOADING = 2, 6 | ERROR = 3, 7 | } 8 | 9 | export interface ImportKey { 10 | id: string; 11 | fingerprint: string; 12 | privateKey: OpenPGPKey; 13 | status: Status; 14 | result?: 'ok' | Error; 15 | } 16 | -------------------------------------------------------------------------------- /components/vr/Vr.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | export interface Props { 5 | className?: string; 6 | } 7 | 8 | // Vr for Vertical Rule 9 | const Vr = ({ className = '', ...rest }: Props) => { 10 | return ; 11 | }; 12 | 13 | export default Vr; 14 | -------------------------------------------------------------------------------- /components/dropzone/helpers.ts: -------------------------------------------------------------------------------- 1 | import { DragEvent, DragEventHandler } from 'react'; 2 | 3 | export const isDragFile = (event: DragEvent) => event.dataTransfer?.types.includes('Files'); 4 | 5 | export const onlyDragFiles = (eventHandler: DragEventHandler) => (event: DragEvent) => { 6 | if (isDragFile(event)) { 7 | return eventHandler(event); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /components/layout/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PrivateMainArea } from './PrivateMainArea'; 2 | export { default as PrivateMainSettingsArea } from './PrivateMainSettingsArea'; 3 | export { default as SubSettingsSection } from './SubSettingsSection'; 4 | export { default as SidebarListItemsWithSubsections } from './SidebarListItemsWithSubsections'; 5 | 6 | export * from './interface'; 7 | -------------------------------------------------------------------------------- /containers/keys/importKeys/state.ts: -------------------------------------------------------------------------------- 1 | import { ImportKey } from './interface'; 2 | 3 | export const updateKey = (oldKeys: ImportKey[], id: string, newKey: Partial): ImportKey[] => { 4 | return oldKeys.map((oldKey) => { 5 | if (oldKey.id !== id) { 6 | return oldKey; 7 | } 8 | return { ...oldKey, ...newKey }; 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /components/icon/Icons.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import svg from 'design-system/_includes/sprite-icons.svg'; 3 | import svgFiles from 'design-system/assets/img/sprite-icons/file-icons.svg'; 4 | 5 | export const ICONS_ID = 'icons-root'; 6 | 7 | const Icons = () =>
; 8 | 9 | export default Icons; 10 | -------------------------------------------------------------------------------- /components/topnavbar/index.ts: -------------------------------------------------------------------------------- 1 | export { default as TopNavbar } from './TopNavbar'; 2 | export { default as TopNavbarList } from './TopNavbarList'; 3 | export { default as TopNavbarListItem } from './TopNavbarListItem'; 4 | export { default as TopNavbarListItemButton } from './TopNavbarListItemButton'; 5 | export { default as TopNavbarListItemSearchButton } from './TopNavbarListItemSearchButton'; 6 | -------------------------------------------------------------------------------- /containers/login/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AbuseModal } from './AbuseModal'; 2 | export { default as MinimalForgotUsernameContainer } from '../forgotUsername/MinimalForgotUsernameContainer'; 3 | export { default as MinimalLoginContainer } from './MinimalLoginContainer'; 4 | export { default as FooterDetails } from './FooterDetails'; 5 | export { default as UnlockModal } from './UnlockModal'; 6 | -------------------------------------------------------------------------------- /containers/paymentMethods/interface.ts: -------------------------------------------------------------------------------- 1 | import { PAYMENT_METHOD_TYPE } from 'proton-shared/lib/constants'; 2 | 3 | export interface PaymentMethodData { 4 | icon: string; 5 | value: PAYMENT_METHOD_TYPE; 6 | text: string; 7 | disabled?: boolean; 8 | } 9 | export type PaymentMethodFlows = 'invoice' | 'signup' | 'human-verification' | 'credit' | 'donation' | 'subscription'; 10 | -------------------------------------------------------------------------------- /containers/account/SettingsLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { classnames } from '../../helpers'; 4 | import './SettingsLayout.scss'; 5 | 6 | const SettingsLayout = ({ className = '', ...rest }: React.ComponentPropsWithoutRef<'div'>) => { 7 | return
; 8 | }; 9 | 10 | export default SettingsLayout; 11 | -------------------------------------------------------------------------------- /containers/account/SettingsPageTitle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { classnames } from '../../helpers'; 4 | 5 | const SettingsPageTitle = ({ className, children, ...rest }: React.HTMLAttributes) => ( 6 |

7 | {children} 8 |

9 | ); 10 | 11 | export default SettingsPageTitle; 12 | -------------------------------------------------------------------------------- /containers/calendar/settings/CalendarsTable.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/variables'; 2 | @import '~design-system/scss/config/functions/easing'; 3 | @import '~design-system/scss/specifics/placeholder-loading'; 4 | 5 | .calendar-email { 6 | color: green; 7 | 8 | &::before { 9 | @extend %item-loading-pseudo; 10 | @extend %placeholder-loading; 11 | width: 70%; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /containers/filters/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SpamFiltersSection } from './SpamFiltersSection'; 2 | export { default as FiltersSection } from './FiltersSection'; 3 | export { default as AddEmailToListModal } from './AddEmailToListModal'; 4 | export { default as AddFilterModal } from './modal/FilterModal'; 5 | 6 | export * as FilterUtils from './utils'; 7 | export * as FilterConstants from './constants'; 8 | -------------------------------------------------------------------------------- /components/orderableTable/OrderableTableBody.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { TableBody } from '../table'; 5 | 6 | const OrderableTableBody = ({ colSpan, ...rest }) => ; 7 | 8 | OrderableTableBody.propTypes = { 9 | colSpan: PropTypes.number, 10 | }; 11 | 12 | export default OrderableTableBody; 13 | -------------------------------------------------------------------------------- /components/container/Field.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Field = ({ children, className }) => { 5 | return
{children}
; 6 | }; 7 | 8 | Field.propTypes = { 9 | children: PropTypes.node, 10 | className: PropTypes.string, 11 | }; 12 | 13 | export default Field; 14 | -------------------------------------------------------------------------------- /containers/invoices/interface.ts: -------------------------------------------------------------------------------- 1 | import { INVOICE_STATE } from 'proton-shared/lib/constants'; 2 | 3 | export interface Invoice { 4 | ID: string; 5 | Type: number; 6 | State: INVOICE_STATE; 7 | Currency: string; 8 | AmountDue: number; 9 | AmountCharged: number; 10 | CreateTime: number; 11 | ModifyTime: number; 12 | AttemptTime: number; 13 | Attempts: number; 14 | } 15 | -------------------------------------------------------------------------------- /containers/vpn/OpenVPNConfigurationSection/utils.js: -------------------------------------------------------------------------------- 1 | import { SERVER_FEATURES } from 'proton-shared/lib/constants'; 2 | 3 | export const isFeatureOn = (feature) => (features = 0) => !!(features & feature); 4 | export const isSecureCoreEnabled = isFeatureOn(SERVER_FEATURES.SECURE_CORE); 5 | export const isP2PEnabled = isFeatureOn(SERVER_FEATURES.P2P); 6 | export const isTorEnabled = isFeatureOn(SERVER_FEATURES.TOR); 7 | -------------------------------------------------------------------------------- /containers/app/DelinquentContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DelinquentModal from '../api/DelinquentModal'; 3 | import { ProminentContainer } from '../../components'; 4 | 5 | const DelinquentContainer = () => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default DelinquentContainer; 14 | -------------------------------------------------------------------------------- /containers/items/items.scss: -------------------------------------------------------------------------------- 1 | .drag-element { 2 | position: absolute; 3 | background-color: white; 4 | color: black; 5 | font-weight: bold; 6 | 7 | z-index: -1; 8 | top: 0; 9 | left: 0; 10 | } 11 | 12 | .item-dragging { 13 | opacity: 0.5; 14 | } 15 | 16 | .navigation__dragover { 17 | background: rgba(255, 255, 255, 0.05); 18 | color: var(--color-nav-link, #f6f7fa); 19 | text-decoration: none; 20 | } 21 | -------------------------------------------------------------------------------- /containers/resetPassword/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MinimalResetPasswordContainer } from './MinimalResetPasswordContainer'; 2 | export { default as ResetDangerInput } from './ResetDangerInput'; 3 | export { default as ResetPasswordEmailInput } from './ResetPasswordEmailInput'; 4 | export { default as ResetTokenInput } from './ResetTokenInput'; 5 | export { default as ResetUsernameInput } from './ResetUsernameInput'; 6 | -------------------------------------------------------------------------------- /components/link/LearnMore.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { c } from 'ttag'; 3 | 4 | import Href from './Href'; 5 | 6 | export interface LearnMoreProps { 7 | url: string; 8 | className?: string; 9 | } 10 | 11 | const LearnMore = ({ url, className }: LearnMoreProps) => ( 12 | {c('Link').t`Learn more`} 13 | ); 14 | 15 | export default LearnMore; 16 | -------------------------------------------------------------------------------- /containers/account/SettingsLayoutLeft.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { classnames } from '../../helpers'; 4 | 5 | import './SettingsLayoutLeft.scss'; 6 | 7 | const SettingsLayoutLeft = ({ className, ...rest }: React.ComponentPropsWithoutRef<'div'>) => { 8 | return
; 9 | }; 10 | 11 | export default SettingsLayoutLeft; 12 | -------------------------------------------------------------------------------- /containers/account/SettingsSectionTitle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import SubTitle, { SubTitleProps } from '../../components/title/SubTitle'; 4 | import { classnames } from '../../helpers'; 5 | 6 | const SettingsSectionTitle = ({ className, ...rest }: SubTitleProps) => ( 7 | 8 | ); 9 | 10 | export default SettingsSectionTitle; 11 | -------------------------------------------------------------------------------- /containers/contacts/import/ImportFatalError.ts: -------------------------------------------------------------------------------- 1 | import { c } from 'ttag'; 2 | 3 | export class ImportFatalError extends Error { 4 | error: Error; 5 | 6 | constructor(error: Error) { 7 | super(c('Error importing calendar').t`An unexpected error occurred. Import must be restarted.`); 8 | this.error = error; 9 | Object.setPrototypeOf(this, ImportFatalError.prototype); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /containers/filters/modal/FilterConditionsFormRow.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .condition-token { 4 | border-radius: 1em; 5 | border: 1px solid var(--border-norm); 6 | height: 1.6em; 7 | padding: 0 0.6em; 8 | align-items: center; 9 | font-size: rem(14); 10 | color: var(--text-norm); 11 | background: var(--background-weak); 12 | 13 | & > span { 14 | max-width: 26em; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /components/collapsingBreadcrumbs/interfaces.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface BreadcrumbInfo 4 | extends Omit, 'children' | 'onClick' | 'className'> { 5 | key: string | number; 6 | text: string; 7 | collapsedText?: React.ReactNode; 8 | noShrink?: boolean; 9 | highlighted?: boolean; 10 | onClick?: () => void; 11 | } 12 | -------------------------------------------------------------------------------- /components/loader/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useLoader } from './useLoader'; 2 | export { default as Loader } from './Loader'; 3 | export { default as FullLoader } from './FullLoader'; 4 | export { default as EllipsisLoader } from './EllipsisLoader'; 5 | export { default as TextLoader } from './TextLoader'; 6 | export { default as CircleLoader } from './CircleLoader'; 7 | export { default as LoaderIcon } from './LoaderIcon'; 8 | -------------------------------------------------------------------------------- /containers/account/SettingsLayoutRight.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { classnames } from '../../helpers'; 4 | 5 | import './SettingsLayoutRight.scss'; 6 | 7 | const SettingsLayoutRight = ({ className, ...rest }: React.ComponentPropsWithoutRef<'div'>) => { 8 | return
; 9 | }; 10 | 11 | export default SettingsLayoutRight; 12 | -------------------------------------------------------------------------------- /containers/addresses/missingKeys/state.ts: -------------------------------------------------------------------------------- 1 | import { AddressWithStatus } from './interface'; 2 | 3 | export const updateAddress = (oldAddresses: AddressWithStatus[], ID: string, diff: Partial) => { 4 | return oldAddresses.map((oldAddress) => { 5 | if (oldAddress.ID === ID) { 6 | return { ...oldAddress, ...diff }; 7 | } 8 | return oldAddress; 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /containers/filePreview/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FilePreview } from './FilePreview'; 2 | export { default as NavigationControl } from './NavigationControl'; 3 | export { default as PDFPreview } from './PDFPreview'; 4 | export { default as ImagePreview } from './ImagePreview'; 5 | export { default as TextPreview } from './TextPreview'; 6 | export { default as ZoomControl } from './ZoomControl'; 7 | export * from './helpers'; 8 | -------------------------------------------------------------------------------- /containers/topBanners/PublicTopBanners.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import OnlineTopBanner from './OnlineTopBanner'; 4 | import NewVersionTopBanner from './NewVersionTopBanner'; 5 | 6 | const PublicTopBanners = () => { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default PublicTopBanners; 16 | -------------------------------------------------------------------------------- /hooks/useEventManager.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import Context from '../containers/eventManager/context'; 3 | 4 | function useEventManager() { 5 | const eventManager = useContext(Context); 6 | 7 | if (!eventManager) { 8 | throw new Error('Trying to use uninitialized EventManagerContext'); 9 | } 10 | 11 | return eventManager; 12 | } 13 | 14 | export default useEventManager; 15 | -------------------------------------------------------------------------------- /hooks/useMainArea.ts: -------------------------------------------------------------------------------- 1 | import { useContext, createContext } from 'react'; 2 | 3 | export const MainAreaContext = createContext | null>(null); 4 | export const useMainArea = () => { 5 | const mainArea = useContext(MainAreaContext); 6 | 7 | if (mainArea === null) { 8 | throw new Error('Trying to use uninitialized MainAreaContext'); 9 | } 10 | 11 | return mainArea; 12 | }; 13 | -------------------------------------------------------------------------------- /components/container/Block.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { classnames } from '../../helpers'; 4 | 5 | const Block = ({ children, className = '' }) => { 6 | return
{children}
; 7 | }; 8 | 9 | Block.propTypes = { 10 | children: PropTypes.node, 11 | className: PropTypes.string, 12 | }; 13 | 14 | export default Block; 15 | -------------------------------------------------------------------------------- /components/link/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Info } from './Info'; 2 | export { default as Href } from './Href'; 3 | export { default as useAppLink } from './useAppLink'; 4 | export { default as useSettingsLink } from './useSettingsLink'; 5 | export { default as AppLink } from './AppLink'; 6 | export { default as SettingsLink } from './SettingsLink'; 7 | export { default as LearnMore } from './LearnMore'; 8 | export * from './LearnMore'; 9 | -------------------------------------------------------------------------------- /typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg'; 2 | 3 | declare module '*.jpg'; 4 | 5 | declare module '*.md'; 6 | 7 | // TODO: Import from proton-shared 8 | declare module 'ical.js'; 9 | 10 | declare module 'squire-rte'; 11 | 12 | declare module 'pm-srp'; 13 | 14 | declare module 'is-valid-domain'; 15 | 16 | // Broken types from imagemin-webpack-plugin 17 | declare module 'svgo' { 18 | export interface Options {} 19 | } 20 | -------------------------------------------------------------------------------- /components/button/ErrorButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button, { ButtonProps } from './Button'; 3 | 4 | export type ErrorButtonProps = Omit; 5 | 6 | const ErrorButton = (props: ErrorButtonProps, ref: React.Ref) => { 7 | return 14 | ); 15 | }; 16 | 17 | export default BackButton; 18 | -------------------------------------------------------------------------------- /containers/authentication/Provider.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PrivateAuthenticationStore, PublicAuthenticationStore } from '../app/interface'; 3 | 4 | import AuthenticationContext from './authenticationContext'; 5 | 6 | interface Props { 7 | children?: React.ReactNode; 8 | store: PrivateAuthenticationStore | PublicAuthenticationStore; 9 | } 10 | const AuthenticationProvider = ({ store, children }: Props) => { 11 | return {children}; 12 | }; 13 | 14 | export default AuthenticationProvider; 15 | -------------------------------------------------------------------------------- /containers/topBanners/SubUserTopBanner.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { c } from 'ttag'; 3 | 4 | import { useUser } from '../../hooks'; 5 | import TopBanner from './TopBanner'; 6 | 7 | const SubUserTopBanner = () => { 8 | const [user] = useUser(); 9 | 10 | if (!user.isSubUser) { 11 | return null; 12 | } 13 | 14 | return ( 15 | {c('Info') 16 | .t`You are currently signed in as ${user.Name} (${user.Email}) and have restricted access.`} 17 | ); 18 | }; 19 | 20 | export default SubUserTopBanner; 21 | -------------------------------------------------------------------------------- /components/modal/FormModal.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import FormModal from './FormModal'; 5 | 6 | describe('Modal component', () => { 7 | const mockOnClose = jest.fn(); 8 | const content =
panda
; 9 | 10 | it('should render the modal content', () => { 11 | const { container } = render( 12 | 13 | {content} 14 | 15 | ); 16 | expect(container.firstChild).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /components/sidebar/MobileNavLink.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import AppLink, { Props as AppLinkProps } from '../link/AppLink'; 3 | import Icon from '../icon/Icon'; 4 | 5 | interface Props extends AppLinkProps { 6 | icon: string; 7 | current: boolean; 8 | } 9 | const MobileNavLink = ({ icon = '', current = false, ...rest }: Props) => { 10 | return ( 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default MobileNavLink; 18 | -------------------------------------------------------------------------------- /containers/app/LocationErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | import ErrorBoundary from './ErrorBoundary'; 4 | 5 | interface Props { 6 | children: React.ReactNode; 7 | component?: React.ReactNode; 8 | } 9 | 10 | const LocationErrorBoundary = ({ children, component }: Props) => { 11 | const location = useLocation(); 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | }; 18 | 19 | export default LocationErrorBoundary; 20 | -------------------------------------------------------------------------------- /containers/importAssistant/mail/constants.ts: -------------------------------------------------------------------------------- 1 | import { c } from 'ttag'; 2 | import { OAUTH_PROVIDER } from '../interfaces'; 3 | import { TIME_UNIT } from './interfaces'; 4 | 5 | export const timeUnitLabels = { 6 | [TIME_UNIT.BIG_BANG]: c('Label').t`Import all messages`, 7 | [TIME_UNIT.LAST_YEAR]: c('Label').t`Last 12 months only`, 8 | [TIME_UNIT.LAST_3_MONTHS]: c('Label').t`Last 3 months only`, 9 | [TIME_UNIT.LAST_MONTH]: c('Label').t`Last month only`, 10 | }; 11 | 12 | export const IMAPS = { 13 | [OAUTH_PROVIDER.GOOGLE]: 'imap.gmail.com', 14 | YAHOO: 'imap.mail.yahoo.com', 15 | }; 16 | -------------------------------------------------------------------------------- /components/input/LegacyInputField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | interface Props { 5 | label: React.ReactNode; 6 | input: React.ReactNode; 7 | className?: string; 8 | } 9 | const LegacyInputField = ({ label, input, className }: Props) => { 10 | return ( 11 |
12 | {label} 13 |
{input}
14 |
15 | ); 16 | }; 17 | 18 | export default LegacyInputField; 19 | -------------------------------------------------------------------------------- /components/table/TableFooter.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TableCell from './TableCell'; 3 | 4 | interface Props extends React.HTMLAttributes { 5 | cells: React.ReactNode[]; 6 | } 7 | 8 | const TableFooter = ({ cells = [], ...rest }: Props) => { 9 | return ( 10 | 11 | 12 | {cells.map((cell, index) => ( 13 | {cell} 14 | ))} 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default TableFooter; 21 | -------------------------------------------------------------------------------- /components/table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Table } from './Table'; 2 | export { default as TableCell } from './TableCell'; 3 | export { default as TableHeader } from './TableHeader'; 4 | export { default as TableFooter } from './TableFooter'; 5 | export { default as TableRow } from './TableRow'; 6 | export { default as TableBody } from './TableBody'; 7 | export { default as TableRowSticky } from './TableRowSticky'; 8 | export { default as TableRowBusy } from './TableRowBusy'; 9 | export { default as TableHeaderCell } from './TableHeaderCell'; 10 | export { default as TableCellBusy } from './TableCellBusy'; 11 | -------------------------------------------------------------------------------- /containers/notifications/interfaces.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export type NotificationType = 'error' | 'warning' | 'info' | 'success'; 4 | 5 | export interface NotificationOptions { 6 | id: number; 7 | key: any; 8 | text: React.ReactNode; 9 | type: NotificationType; 10 | isClosing: boolean; 11 | disableAutoClose?: boolean; 12 | } 13 | 14 | export interface CreateNotificationOptions extends Omit { 15 | id?: number; 16 | type?: NotificationType; 17 | isClosing?: boolean; 18 | expiration?: number; 19 | } 20 | -------------------------------------------------------------------------------- /containers/payments/subscription/UpsellItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Icon } from '../../../components'; 4 | 5 | interface UpsellItemProps { 6 | icon: string; 7 | children: React.ReactNode; 8 | } 9 | 10 | const UpsellItem = ({ icon, children }: UpsellItemProps) => { 11 | return ( 12 |
13 | 14 | {children} 15 |
16 | ); 17 | }; 18 | 19 | export default UpsellItem; 20 | -------------------------------------------------------------------------------- /containers/sessions/interface.ts: -------------------------------------------------------------------------------- 1 | import { getClientsI18N } from './helper'; 2 | 3 | type ClientIDs = keyof ReturnType; 4 | 5 | export interface Session { 6 | ExpirationTime: number; 7 | UnlockExpirationTime: number; 8 | ClientID: ClientIDs; 9 | CreateTime: number; 10 | Scope: number; 11 | ParentUID: null; 12 | RefreshCounter: number; 13 | Flags: number; 14 | AccessExpirationTime: number; 15 | UID: string; 16 | Algo: number; 17 | UserID: number; 18 | OwnerUserID: number | null; 19 | MemberID: string; 20 | Revocable: 1 | 0; 21 | } 22 | -------------------------------------------------------------------------------- /components/input/EmailInput.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { c } from 'ttag'; 3 | import { validateEmailAddress } from 'proton-shared/lib/helpers/email'; 4 | import { EMAIL_PLACEHOLDER } from 'proton-shared/lib/constants'; 5 | 6 | import Input, { Props } from './Input'; 7 | 8 | const EmailInput = ({ value = '', ...rest }: Props) => { 9 | const error = value ? (validateEmailAddress(value as string) ? '' : c('Error').t`Email address invalid`) : ''; 10 | return ; 11 | }; 12 | 13 | export default EmailInput; 14 | -------------------------------------------------------------------------------- /containers/api/humanVerification/helper.ts: -------------------------------------------------------------------------------- 1 | import { queryVerificationCode } from 'proton-shared/lib/api/user'; 2 | import { VerificationModel } from './interface'; 3 | 4 | export const getFormattedCode = (value: string, code: string) => { 5 | return `${value}:${code}`.replace(/\s/g, ''); 6 | }; 7 | 8 | export const getRoute = (verificationModel: VerificationModel) => { 9 | if (verificationModel.method === 'email') { 10 | return queryVerificationCode('email', { Address: verificationModel.value }); 11 | } 12 | return queryVerificationCode('sms', { Phone: verificationModel.value }); 13 | }; 14 | -------------------------------------------------------------------------------- /containers/paymentMethods/index.ts: -------------------------------------------------------------------------------- 1 | export { default as PaymentMethodState } from './PaymentMethodState'; 2 | export { default as PaymentMethodActions } from './PaymentMethodActions'; 3 | export { default as PaymentMethodDetails } from './PaymentMethodDetails'; 4 | export { default as PaymentMethodsTable } from './PaymentMethodsTable'; 5 | export { default as PaymentMethodsSection } from './PaymentMethodsSection'; 6 | export { default as PaymentMethodSelector } from './PaymentMethodSelector'; 7 | export { default as useMethods } from './useMethods'; 8 | export { default as PaymentMethodsSelect } from './PaymentMethodsSelect'; 9 | -------------------------------------------------------------------------------- /components/avatar/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { classnames } from '../../helpers'; 4 | 5 | interface AvatarProps extends React.ComponentPropsWithoutRef<'span'> {} 6 | 7 | const Avatar = ({ className: classNameProp, children, ...rest }: AvatarProps) => { 8 | const className = classnames([ 9 | classNameProp, 10 | 'avatar rounded inline-flex flex-justify-center flex-align-items-center', 11 | ]); 12 | 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | }; 19 | 20 | export default Avatar; 21 | -------------------------------------------------------------------------------- /components/link/Href.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface Props extends React.AnchorHTMLAttributes { 4 | url?: string; 5 | target?: string; 6 | rel?: string; 7 | children?: React.ReactNode; 8 | } 9 | 10 | const Href = ( 11 | { url = '#', target = '_blank', rel = 'noopener noreferrer nofollow', children, ...rest }: Props, 12 | ref: React.Ref 13 | ) => ( 14 | 15 | {children} 16 | 17 | ); 18 | 19 | export default React.forwardRef(Href); 20 | -------------------------------------------------------------------------------- /components/topnavbar/TopNavbarListItem.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | interface Props extends React.ComponentPropsWithoutRef<'li'> { 5 | noShrink?: boolean; 6 | } 7 | 8 | const TopNavbarListItem = ({ children, noShrink, className, ...rest }: Props) => { 9 | if (!React.isValidElement(children)) { 10 | return null; 11 | } 12 | return ( 13 |
  • 14 | {children} 15 |
  • 16 | ); 17 | }; 18 | 19 | export default TopNavbarListItem; 20 | -------------------------------------------------------------------------------- /hooks/useSynchronizingState.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | /* 4 | * Same as setState with the difference being that the value 5 | * passed in the argument of the hook is not only an initial 6 | * value but will synchronize with the returned state should 7 | * it change (pointer identity). 8 | */ 9 | const useSynchronizingState = (value: V) => { 10 | const [state, setState] = useState(value); 11 | 12 | useEffect(() => { 13 | setState(value); 14 | }, [value]); 15 | 16 | return [state, setState] as const; 17 | }; 18 | 19 | export default useSynchronizingState; 20 | -------------------------------------------------------------------------------- /containers/app/StandardErrorPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { c } from 'ttag'; 3 | import GenericError from '../error/GenericError'; 4 | import { ProminentContainer } from '../../components'; 5 | import { useDocumentTitle } from '../../hooks'; 6 | 7 | const StandardErrorPage = () => { 8 | useDocumentTitle(c('Error message').t`Oops, something went wrong`); 9 | 10 | return ( 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default StandardErrorPage; 18 | -------------------------------------------------------------------------------- /containers/filePreview/PreviewLoader.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { c } from 'ttag'; 3 | import FullLoader from '../../components/loader/FullLoader'; 4 | import TextLoader from '../../components/loader/TextLoader'; 5 | 6 | const PreviewLoader = () => { 7 | return ( 8 |
    9 |
    10 | 11 | {c('Info').t`Loading preview`} 12 |
    13 |
    14 | ); 15 | }; 16 | 17 | export default PreviewLoader; 18 | -------------------------------------------------------------------------------- /containers/invoices/InvoiceAmount.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { INVOICE_STATE } from 'proton-shared/lib/constants'; 3 | import { Price } from '../../components'; 4 | import { Invoice } from './interface'; 5 | 6 | interface Props { 7 | invoice: Invoice; 8 | } 9 | 10 | const format = ({ State, AmountCharged = 0, AmountDue = 0 }: Invoice) => { 11 | return State === INVOICE_STATE.UNPAID ? AmountDue : AmountCharged; 12 | }; 13 | 14 | const InvoiceAmount = ({ invoice }: Props) => { 15 | return {format(invoice)}; 16 | }; 17 | 18 | export default InvoiceAmount; 19 | -------------------------------------------------------------------------------- /hooks/useAppTitle.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { getAppName } from 'proton-shared/lib/apps/helper'; 3 | 4 | import useConfig from './useConfig'; 5 | import useDocumentTitle from './useDocumentTitle'; 6 | 7 | const useAppTitle = (title: string, maybeAppName?: string) => { 8 | const { APP_NAME } = useConfig(); 9 | 10 | const memoedTitle = useMemo(() => { 11 | const appName = maybeAppName || getAppName(APP_NAME); 12 | return [title, appName].filter(Boolean).join(' - '); 13 | }, [title]); 14 | 15 | useDocumentTitle(memoedTitle); 16 | }; 17 | 18 | export default useAppTitle; 19 | -------------------------------------------------------------------------------- /containers/vpn/OpenVPNConfigurationSection/ServerNumber.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { c, msgid } from 'ttag'; 3 | import { VPNServer } from 'proton-shared/lib/interfaces/VPNServer'; 4 | 5 | interface Props { 6 | group: VPNServer[]; 7 | } 8 | 9 | const ServerNumber = ({ group }: Props) => { 10 | const number = group.reduce((acc, { Servers }) => acc + Servers.length, 0); 11 | return ( 12 |
    13 | {c('Info').ngettext(msgid`${number} server`, `${number} servers`, number)} 14 |
    15 | ); 16 | }; 17 | 18 | export default ServerNumber; 19 | -------------------------------------------------------------------------------- /hooks/useLocalState.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { getItem, setItem } from 'proton-shared/lib/helpers/storage'; 3 | 4 | const useLocalState = (defaultValue: any, key: string) => { 5 | const [value, setValue] = useState(() => { 6 | const stickyValue = getItem(key); 7 | 8 | return stickyValue !== null && stickyValue !== undefined ? JSON.parse(stickyValue) : defaultValue; 9 | }); 10 | 11 | useEffect(() => { 12 | setItem(key, JSON.stringify(value)); 13 | }, [key, value]); 14 | 15 | return [value, setValue]; 16 | }; 17 | 18 | export default useLocalState; 19 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | setupFilesAfterEnv: ['./rtl.setup.js'], 3 | verbose: true, 4 | moduleDirectories: ['node_modules'], 5 | transform: { 6 | '^.+\\.(js|tsx?)$': 'babel-jest' 7 | }, 8 | moduleNameMapper: { 9 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)$': '/__mocks__/fileMock.js', 10 | '\\.(css|scss|less)$': '/__mocks__/styleMock.js', 11 | pmcrypto: '/__mocks__/pmcrypto.js', 12 | 'sieve.js': '/__mocks__/sieve.js' 13 | }, 14 | transformIgnorePatterns: ['node_modules/(?!(proton-shared)/)'] 15 | }; 16 | -------------------------------------------------------------------------------- /components/input/LazyRichTextEditor.js: -------------------------------------------------------------------------------- 1 | import React, { lazy, Suspense } from 'react'; 2 | import Loader from '../loader/Loader'; 3 | 4 | // lazy load RichTextEditor component to avoid loading unnecessary css styles 5 | const LazyRichTextEditor = lazy(() => import('./RichTextEditor/RichTextEditor')); 6 | 7 | // we need to suspend, otherwise the app will crash while loading 8 | const SuspendedLazyRichTextEditor = (props) => { 9 | return ( 10 | }> 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default SuspendedLazyRichTextEditor; 17 | -------------------------------------------------------------------------------- /components/progress/Progress.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { classnames } from '../../helpers'; 4 | 5 | interface Props { 6 | value?: number; 7 | max?: number; 8 | id?: string; 9 | className?: string; 10 | } 11 | 12 | const Progress = ({ value = 50, max = 100, id, className, ...rest }: Props) => { 13 | return ( 14 | 21 | ); 22 | }; 23 | 24 | export default Progress; 25 | -------------------------------------------------------------------------------- /containers/addresses/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useAddressModel } from './useAddressModel'; 2 | export { default as AddressActions } from './AddressActions'; 3 | export { default as DomainsSelect } from './DomainsSelect'; 4 | export { default as EditAddressModal } from './EditAddressModal'; 5 | export { default as AddressModal } from './AddressModal'; 6 | export { default as AddressesSection } from './AddressesSection'; 7 | export { default as IdentitySection } from './IdentitySection'; 8 | export { default as AddressesWithUser } from './AddressesWithUser'; 9 | export { default as AddressesWithMembers } from './AddressesWithMembers'; 10 | -------------------------------------------------------------------------------- /containers/contacts/widget/ContactsWidget.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .contacts-widget { 4 | --min-width: #{em(500)}; 5 | --min-height: #{em(550)}; 6 | 7 | &-search-container, 8 | &-toolbar, 9 | .item-container { 10 | padding-left: em(24); 11 | padding-right: em(24); 12 | } 13 | &-tabs { 14 | padding-top: em(12); 15 | margin-left: em(24); 16 | margin-right: em(24); 17 | } 18 | .ReactVirtualized__Grid__innerScrollContainer { 19 | border: 0; 20 | } 21 | @include respond-to($breakpoint-small) { 22 | &.dropdown { 23 | padding-left: 5%; 24 | padding-right: 5%; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /components/button/InlineLinkButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | type Props = React.DetailedHTMLProps, HTMLButtonElement>; 5 | 6 | const InlineLinkButton = ({ children, className = '', ...rest }: Props, ref: React.Ref) => { 7 | return ( 8 | 11 | ); 12 | }; 13 | 14 | export default React.forwardRef(InlineLinkButton); 15 | -------------------------------------------------------------------------------- /containers/cache/Provider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { Cache } from 'proton-shared/lib/helpers/cache'; 3 | import CacheContext from './cacheContext'; 4 | 5 | interface Props { 6 | cache: Cache; 7 | children: React.ReactNode; 8 | } 9 | const Provider = ({ cache, children }: Props) => { 10 | useEffect(() => { 11 | return () => { 12 | cache.clear(); 13 | cache.clearListeners(); 14 | }; 15 | }, []); 16 | 17 | return {children}; 18 | }; 19 | 20 | export default Provider; 21 | -------------------------------------------------------------------------------- /containers/members/index.ts: -------------------------------------------------------------------------------- 1 | export { default as UsersAndAddressesSection } from './UsersAndAddressesSection'; 2 | export { default as EditMemberModal } from './EditMemberModal'; 3 | export { default as MemberRole } from './MemberRole'; 4 | export { default as MemberStorageSelector } from './MemberStorageSelector'; 5 | export { default as MemberFeatures } from './MemberFeatures'; 6 | export { default as MemberActions } from './MemberActions'; 7 | export { default as MemberVPNSelector } from './MemberVPNSelector'; 8 | export { default as MemberModal } from './MemberModal'; 9 | export { default as MemberAddresses } from './MemberAddresses'; 10 | -------------------------------------------------------------------------------- /containers/payments/useCard.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | import { getErrors } from './cardValidator'; 4 | import getDefaultCard from './getDefaultCard'; 5 | 6 | import { CardModel } from './interface'; 7 | 8 | const useCard = (initialCard = getDefaultCard()) => { 9 | const [card, update] = useState(initialCard); 10 | const setCard = (key: string, value: string) => update((card) => ({ ...card, [key]: value })); 11 | const errors = getErrors(card); 12 | const isValid = !Object.keys(errors).length; 13 | 14 | return { card, setCard, errors, isValid }; 15 | }; 16 | 17 | export default useCard; 18 | -------------------------------------------------------------------------------- /hooks/useFeature.ts: -------------------------------------------------------------------------------- 1 | import { useContext, useEffect } from 'react'; 2 | 3 | import { FeatureCode, FeaturesContext } from '../containers/features'; 4 | 5 | const useFeature = (code: FeatureCode) => { 6 | const { features, loading, get, put } = useContext(FeaturesContext); 7 | 8 | useEffect(() => { 9 | get(code); 10 | }, []); 11 | 12 | return { 13 | get: () => get(code), 14 | update: (value: V) => put(code, value), 15 | feature: features[code], 16 | loading: loading[code] === undefined || loading[code], 17 | }; 18 | }; 19 | 20 | export default useFeature; 21 | -------------------------------------------------------------------------------- /components/layout/interface.ts: -------------------------------------------------------------------------------- 1 | import * as H from 'history'; 2 | import { PERMISSIONS } from 'proton-shared/lib/constants'; 3 | 4 | export interface SettingsPropsShared { 5 | location: H.Location; 6 | setActiveSection?: (section: string) => void; 7 | } 8 | 9 | export interface SubSectionConfig { 10 | text?: string; 11 | id: string; 12 | hide?: boolean; 13 | permissions?: PERMISSIONS[]; 14 | } 15 | 16 | export interface SectionConfig { 17 | text: string; 18 | to: string; 19 | icon: string; 20 | description?: string; 21 | subsections?: SubSectionConfig[]; 22 | permissions?: PERMISSIONS[]; 23 | } 24 | -------------------------------------------------------------------------------- /components/topnavbar/TopNavbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { classnames } from '../../helpers'; 4 | 5 | interface Props extends React.ComponentPropsWithoutRef<'div'> {} 6 | 7 | const TopNavbar = ({ children, className, ...rest }: Props) => { 8 | return ( 9 |
    16 | {children} 17 |
    18 | ); 19 | }; 20 | 21 | export default TopNavbar; 22 | -------------------------------------------------------------------------------- /components/table/TableRow.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import TableCell from './TableCell'; 4 | 5 | interface Props extends React.HTMLAttributes { 6 | cells?: React.ReactNode[]; 7 | children?: React.ReactNode; 8 | } 9 | 10 | const TableRow = React.forwardRef(({ cells = [], children, ...rest }, ref) => { 11 | return ( 12 | 13 | {children || cells.map((cell, index) => {cell})} 14 | 15 | ); 16 | }); 17 | 18 | export default TableRow; 19 | 20 | TableRow.displayName = 'TableRow'; 21 | -------------------------------------------------------------------------------- /helpers/component.ts: -------------------------------------------------------------------------------- 1 | import isTruthy from 'proton-shared/lib/helpers/isTruthy'; 2 | 3 | let current = 0; 4 | 5 | export const generateUID = (prefix?: string) => `${prefix || 'id'}-${current++}`; 6 | export const fakeEvent = (value: T) => ({ target: { value } }); 7 | 8 | export const concatStringProp = (strings: (string | boolean | null | undefined)[] = []) => { 9 | return strings.filter(isTruthy).join(' ').trim(); 10 | }; 11 | 12 | /** 13 | * Join CSS classes into string for className prop 14 | */ 15 | export const classnames = (classNames: (string | boolean | null | undefined)[] = []) => { 16 | return concatStringProp(classNames); 17 | }; 18 | -------------------------------------------------------------------------------- /components/button/FloatingButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button, { ButtonProps } from './Button'; 3 | import { classnames } from '../../helpers'; 4 | 5 | interface Props extends ButtonProps { 6 | title?: string; 7 | className?: string; 8 | } 9 | 10 | const FloatingButton = ({ children, title, className, ...rest }: Props, ref: React.Ref) => { 11 | return ( 12 | 15 | ); 16 | }; 17 | 18 | export default React.forwardRef(FloatingButton); 19 | -------------------------------------------------------------------------------- /components/modal/Inner.tsx: -------------------------------------------------------------------------------- 1 | import React, { Ref } from 'react'; 2 | import { classnames } from '../../helpers'; 3 | import ScrollShadows from '../scroll/ScrollShadows'; 4 | 5 | interface Props { 6 | children: React.ReactNode; 7 | className?: string; 8 | } 9 | const Inner = React.forwardRef( 10 | ({ children, className = '' }: Props, ref: Ref) => { 11 | return ( 12 |
    13 | {children} 14 |
    15 | ); 16 | } 17 | ); 18 | 19 | export default Inner; 20 | -------------------------------------------------------------------------------- /components/popper/usePopperAnchor.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useRef, useState } from 'react'; 2 | 3 | const usePopperAnchor = () => { 4 | const anchorRef = useRef(null); 5 | const [isOpen, setOpen] = useState(false); 6 | 7 | const open = useCallback(() => { 8 | setOpen(true); 9 | }, []); 10 | 11 | const close = useCallback(() => { 12 | setOpen(false); 13 | }, []); 14 | 15 | const toggle = useCallback(() => { 16 | return isOpen ? close() : open(); 17 | }, [isOpen]); 18 | 19 | return { anchorRef, isOpen, toggle, open, close }; 20 | }; 21 | 22 | export default usePopperAnchor; 23 | -------------------------------------------------------------------------------- /components/button/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Button } from './Button'; 2 | export * from './Button'; 3 | export { default as ButtonLike } from './ButtonLike'; 4 | export * from './ButtonLike'; 5 | export { default as Copy } from './Copy'; 6 | export { default as ErrorButton } from './ErrorButton'; 7 | export { default as FloatingButton } from './FloatingButton'; 8 | export { default as InlineLinkButton } from './InlineLinkButton'; 9 | export { default as PrimaryButton } from './PrimaryButton'; 10 | export { default as ButtonGroup } from './ButtonGroup'; 11 | export { default as LinkButton } from './LinkButton'; 12 | export { default as FileButton } from './FileButton'; 13 | -------------------------------------------------------------------------------- /containers/general/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MailGeneralAdvancedSection } from './AdvancedSection'; 2 | export { default as LanguageAndTimeSection } from './LanguageAndTimeSection'; 3 | export { default as LanguageSection } from './LanguageSection'; 4 | export { default as AutoSaveContactsToggle } from './AutoSaveContactsToggle'; 5 | export { default as TimeFormatSection } from './TimeFormatSection'; 6 | export { default as DateFormatSection } from './DateFormatSection'; 7 | export { default as WeekStartSection } from './WeekStartSection'; 8 | export { default as ShortcutsToggle } from './ShortcutsToggle'; 9 | export { default as PmMeSection } from './PmMeSection'; 10 | -------------------------------------------------------------------------------- /containers/vpn/OpenVPNConfigurationSection/CityNumber.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { c, msgid } from 'ttag'; 3 | import { VPNServer } from 'proton-shared/lib/interfaces/VPNServer'; 4 | import { uniqueBy } from 'proton-shared/lib/helpers/array'; 5 | 6 | interface Props { 7 | group: VPNServer[]; 8 | } 9 | 10 | const CityNumber = ({ group }: Props) => { 11 | const number = uniqueBy(group, ({ City }) => City).length; 12 | return ( 13 |
    14 | {c('Info').ngettext(msgid`${number} city`, `${number} cities`, number)} 15 |
    16 | ); 17 | }; 18 | 19 | export default CityNumber; 20 | -------------------------------------------------------------------------------- /hooks/useCyberMondayPeriod.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { isCyberMonday } from 'proton-shared/lib/helpers/blackfriday'; 3 | 4 | const EVERY_MINUTE = 60 * 1000; 5 | 6 | const useCyberMondayPeriod = () => { 7 | const [cyberMonday, setCyberMonday] = useState(isCyberMonday()); 8 | 9 | useEffect(() => { 10 | const intervalID = setInterval(() => { 11 | setCyberMonday(isCyberMonday()); 12 | }, EVERY_MINUTE); 13 | 14 | return () => { 15 | clearInterval(intervalID); 16 | }; 17 | }, []); 18 | 19 | return cyberMonday; 20 | }; 21 | 22 | export default useCyberMondayPeriod; 23 | -------------------------------------------------------------------------------- /containers/payments/PaymentInfo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PAYMENT_METHOD_TYPE, PAYMENT_METHOD_TYPES } from 'proton-shared/lib/constants'; 3 | import { c } from 'ttag'; 4 | 5 | import { Alert } from '../../components'; 6 | 7 | interface Props { 8 | method?: PAYMENT_METHOD_TYPE; 9 | } 10 | const PaymentInfo = ({ method }: Props) => { 11 | if (method && [PAYMENT_METHOD_TYPES.BITCOIN, PAYMENT_METHOD_TYPES.CASH].includes(method as any)) { 12 | return null; 13 | } 14 | 15 | return {c('Info').t`Your payment details are protected with TLS encryption and Swiss privacy laws.`}; 16 | }; 17 | 18 | export default PaymentInfo; 19 | -------------------------------------------------------------------------------- /hooks/useSvgGraphicsBbox.ts: -------------------------------------------------------------------------------- 1 | import { RefObject, useLayoutEffect, useState } from 'react'; 2 | 3 | // These defaults do not matter 4 | const DEFAULT = { 5 | y: 0, 6 | x: 0, 7 | width: 100, 8 | height: 100, 9 | }; 10 | const useSvgGraphicsBbox = (ref: RefObject, deps: any[] = []) => { 11 | const [bbox, setBbox] = useState(DEFAULT); 12 | 13 | useLayoutEffect(() => { 14 | if (!ref.current) { 15 | setBbox(DEFAULT); 16 | return; 17 | } 18 | setBbox(ref.current.getBBox()); 19 | }, [ref.current, ...deps]); 20 | 21 | return bbox; 22 | }; 23 | 24 | export default useSvgGraphicsBbox; 25 | -------------------------------------------------------------------------------- /components/modal/Title.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | interface Props extends React.DetailedHTMLProps, HTMLHeadingElement> { 5 | id: string; 6 | children: React.ReactNode; 7 | } 8 | 9 | const Title = ({ children, className, ...rest }: Props) => { 10 | return ( 11 |

    17 | {children} 18 |

    19 | ); 20 | }; 21 | 22 | export default Title; 23 | -------------------------------------------------------------------------------- /components/table/Table.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | interface Props extends React.DetailedHTMLProps, HTMLTableElement> { 5 | children: React.ReactNode; 6 | className?: string; 7 | caption?: string; 8 | } 9 | 10 | const Table = ({ children, className, caption, ...props }: Props) => { 11 | return ( 12 | 13 | {caption ? : null} 14 | {children} 15 |
    {caption}
    16 | ); 17 | }; 18 | 19 | export default Table; 20 | -------------------------------------------------------------------------------- /components/container/EditableSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | interface Props extends React.DetailedHTMLProps, HTMLDivElement> { 5 | children?: React.ReactNode; 6 | className?: string; 7 | } 8 | 9 | const EditableSection = ({ children, className = '', ...rest }: Props) => { 10 | return ( 11 |
    15 | {children} 16 |
    17 | ); 18 | }; 19 | 20 | export default EditableSection; 21 | -------------------------------------------------------------------------------- /components/link/LearnMore.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import LearnMore from './LearnMore'; 5 | 6 | describe('LearnMore component', () => { 7 | const url = 'https://protonmail.com'; 8 | 9 | it('should contain "Learn more" and be a link', () => { 10 | const { getByText } = render(); 11 | const linkNode = getByText('Learn more'); 12 | 13 | expect(linkNode.getAttribute('href')).toBe(url); 14 | expect(linkNode.getAttribute('target')).toBe('_blank'); 15 | expect(linkNode.getAttribute('rel')).toBe('noopener noreferrer nofollow'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /components/orderableTable/OrderableTable.scss: -------------------------------------------------------------------------------- 1 | @import '~design-system/scss/config/'; 2 | 3 | .orderableHelper { 4 | z-index: 666; 5 | display: flex; 6 | align-items: center; 7 | color: var(--text-norm); 8 | background: var(--background-norm); 9 | border: solid var(--border-norm); 10 | border-width: 1px 0; 11 | 12 | /** 13 | * We replace td with custom class here because we don't have direct access to it. 14 | * See: https://github.com/ProtonMail/react-components/pull/113/files#r305374317 15 | */ 16 | td { 17 | flex: 1; 18 | border: none; 19 | height: 100%; 20 | &:first-child { 21 | flex-grow: 0; 22 | flex-basis: rem(35); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /components/sidebar/SidebarListItemHeaderButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Icon from '../icon/Icon'; 3 | 4 | interface Props extends React.ButtonHTMLAttributes { 5 | icon: string; 6 | info: string; 7 | } 8 | export const SidebarListItemHeaderLinkButton = ({ info, icon, ...rest }: Props) => { 9 | return ( 10 | 14 | ); 15 | }; 16 | 17 | export default SidebarListItemHeaderLinkButton; 18 | -------------------------------------------------------------------------------- /hooks/useBlackFridayPeriod.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { isBlackFridayPeriod } from 'proton-shared/lib/helpers/blackfriday'; 3 | 4 | const EVERY_MINUTE = 60 * 1000; 5 | 6 | const useBlackFridayPeriod = () => { 7 | const [blackFriday, setBlackFriday] = useState(isBlackFridayPeriod()); 8 | 9 | useEffect(() => { 10 | const intervalID = setInterval(() => { 11 | setBlackFriday(isBlackFridayPeriod()); 12 | }, EVERY_MINUTE); 13 | 14 | return () => { 15 | clearInterval(intervalID); 16 | }; 17 | }, []); 18 | 19 | return blackFriday; 20 | }; 21 | 22 | export default useBlackFridayPeriod; 23 | -------------------------------------------------------------------------------- /hooks/useContactEmailsSortedByName.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { ContactEmail } from 'proton-shared/lib/interfaces/contacts/Contact'; 3 | 4 | import useContactEmails from './useContactEmails'; 5 | 6 | const compareContactEmailByName = (a: ContactEmail, b: ContactEmail) => { 7 | return a.Name.localeCompare(b.Name); 8 | }; 9 | 10 | const useContactEmailsSortedByName = () => { 11 | const [contactEmails, loading] = useContactEmails(); 12 | return useMemo(() => { 13 | return [[...(contactEmails || [])].sort(compareContactEmailByName), loading] as const; 14 | }, [contactEmails]); 15 | }; 16 | 17 | export default useContactEmailsSortedByName; 18 | -------------------------------------------------------------------------------- /hooks/usePremiumDomains.js: -------------------------------------------------------------------------------- 1 | import { queryPremiumDomains } from 'proton-shared/lib/api/domains'; 2 | 3 | import { useCallback } from 'react'; 4 | import useCachedModelResult from './useCachedModelResult'; 5 | import useApi from './useApi'; 6 | import useCache from './useCache'; 7 | 8 | const getPremiumDomains = (api) => api(queryPremiumDomains()).then(({ Domains = [] }) => Domains); 9 | 10 | const usePremiumDomains = () => { 11 | const api = useApi(); 12 | const cache = useCache(); 13 | const miss = useCallback(() => getPremiumDomains(api), [api]); 14 | return useCachedModelResult(cache, 'premiumDomains', miss); 15 | }; 16 | 17 | export default usePremiumDomains; 18 | -------------------------------------------------------------------------------- /components/link/Href.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import Href from './Href'; 5 | 6 | describe('Href component', () => { 7 | const url = 'https://protonmail.com'; 8 | const text = 'panda'; 9 | 10 | it('should render a link with proper attributes', () => { 11 | const { getByText } = render({text}); 12 | const linkNode = getByText(text); 13 | 14 | expect(linkNode.getAttribute('href')).toBe(url); 15 | expect(linkNode.getAttribute('target')).toBe('_blank'); 16 | expect(linkNode.getAttribute('rel')).toBe('noopener noreferrer nofollow'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /components/sidebar/MobileNavServices.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { APPS } from 'proton-shared/lib/constants'; 3 | 4 | import { useConfig, useActiveBreakpoint } from '../../hooks'; 5 | 6 | interface Props { 7 | children: React.ReactNode; 8 | } 9 | const MobileNavServices = ({ children }: Props) => { 10 | const { isNarrow } = useActiveBreakpoint(); 11 | const { APP_NAME } = useConfig(); 12 | 13 | if (!isNarrow || APP_NAME === APPS.PROTONVPN_SETTINGS) { 14 | return null; 15 | } 16 | 17 | return ; 18 | }; 19 | 20 | export default MobileNavServices; 21 | -------------------------------------------------------------------------------- /hooks/useProductPayerPeriod.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { isProductPayerPeriod } from 'proton-shared/lib/helpers/blackfriday'; 3 | 4 | const EVERY_MINUTE = 60 * 1000; 5 | 6 | const useProductPayerPeriod = () => { 7 | const [productPayer, setProductPayer] = useState(isProductPayerPeriod()); 8 | 9 | useEffect(() => { 10 | const intervalID = setInterval(() => { 11 | setProductPayer(isProductPayerPeriod()); 12 | }, EVERY_MINUTE); 13 | 14 | return () => { 15 | clearInterval(intervalID); 16 | }; 17 | }, []); 18 | 19 | return productPayer; 20 | }; 21 | 22 | export default useProductPayerPeriod; 23 | -------------------------------------------------------------------------------- /hooks/useActiveWindow.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | const useActiveWindow = () => { 4 | const [windowIsActive, setWindowIsActive] = useState(true); 5 | const onFocus = () => setWindowIsActive(true); 6 | const onBlur = () => setWindowIsActive(false); 7 | 8 | useEffect(() => { 9 | window.addEventListener('focus', onFocus); 10 | window.addEventListener('blur', onBlur); 11 | 12 | return () => { 13 | window.removeEventListener('focus', onFocus); 14 | window.removeEventListener('blur', onBlur); 15 | }; 16 | }, []); 17 | 18 | return windowIsActive; 19 | }; 20 | 21 | export default useActiveWindow; 22 | -------------------------------------------------------------------------------- /components/container/Summary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Icon from '../icon/Icon'; 4 | 5 | interface Props extends React.HTMLProps { 6 | children: React.ReactNode; 7 | } 8 | 9 | const Summary = ({ children, ...rest }: Props) => ( 10 | // Safari can't set up summary tag as a flex container, tsssss... https://bugs.webkit.org/show_bug.cgi?id=190065 11 | 12 |
    13 | 14 |
    {children}
    15 |
    16 |
    17 | ); 18 | 19 | export default Summary; 20 | -------------------------------------------------------------------------------- /containers/payments/subscription/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SubscriptionModal } from './SubscriptionModal'; 2 | export { default as CancelSubscriptionSection } from './CancelSubscriptionSection'; 3 | export { default as YourPlanSection } from './YourPlanSection'; 4 | export { default as BlackFridayModal } from './BlackFridayModal'; 5 | export { default as MailBlackFridayModal } from './MailBlackFridayModal'; 6 | export { default as VPNBlackFridayModal } from './VPNBlackFridayModal'; 7 | export { default as SubscriptionCheckout } from './SubscriptionCheckout'; 8 | export { default as PlanSelection } from './PlanSelection'; 9 | export { default as PlanSelectionComparison } from './PlanSelectionComparison'; 10 | -------------------------------------------------------------------------------- /components/container/Row.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | interface Props extends React.DetailedHTMLProps, HTMLDivElement> { 5 | children?: React.ReactNode; 6 | className?: string; 7 | collapseOnMobile?: boolean; 8 | } 9 | 10 | const Row = ({ children, className = '', collapseOnMobile = true, ...rest }: Props) => { 11 | return ( 12 |
    16 | {children} 17 |
    18 | ); 19 | }; 20 | 21 | export default Row; 22 | -------------------------------------------------------------------------------- /containers/payments/toDetails.ts: -------------------------------------------------------------------------------- 1 | import { CardModel } from './interface'; 2 | 3 | const formatYear = (year: any) => { 4 | const pre = String(year).length === 2 ? '20' : ''; 5 | return `${pre}${year}`; 6 | }; 7 | 8 | const clear = (v: any) => String(v).trim(); 9 | 10 | const toDetails = ({ number, month: ExpMonth, year, cvc: CVC, fullname, zip: ZIP, country: Country }: CardModel) => { 11 | return { 12 | Name: clear(fullname), 13 | Number: String(number).replace(/\s+/g, ''), 14 | ExpMonth, 15 | ExpYear: formatYear(year), 16 | CVC, // Don't clean ZIP, space is allowed 17 | ZIP, 18 | Country, 19 | }; 20 | }; 21 | 22 | export default toDetails; 23 | -------------------------------------------------------------------------------- /hooks/useGetUserKeysRaw.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { getDecryptedUserKeys } from 'proton-shared/lib/keys'; 3 | 4 | import useAuthentication from './useAuthentication'; 5 | import { useGetUser } from './useUser'; 6 | 7 | export const useGetUserKeysRaw = (): (() => ReturnType) => { 8 | const authentication = useAuthentication(); 9 | const getUser = useGetUser(); 10 | return useCallback(async () => { 11 | const user = await getUser(); 12 | return getDecryptedUserKeys({ 13 | user, 14 | userKeys: user.Keys, 15 | keyPassword: authentication.getPassword(), 16 | }); 17 | }, [getUser]); 18 | }; 19 | -------------------------------------------------------------------------------- /hooks/useUserScopes.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | 3 | import { Api } from 'proton-shared/lib/interfaces'; 4 | import { queryScopes } from 'proton-shared/lib/api/auth'; 5 | 6 | import useApi from './useApi'; 7 | import useCache from './useCache'; 8 | import useCachedModelResult from './useCachedModelResult'; 9 | 10 | const KEY = 'userScopes'; 11 | 12 | const getUserScopes = (api: Api) => api(queryScopes()).then((result: any = {}) => result.Scope); 13 | 14 | export const useUserScopes = () => { 15 | const api = useApi(); 16 | const cache = useCache(); 17 | const miss = useCallback(() => getUserScopes(api), [api]); 18 | 19 | return useCachedModelResult(cache, KEY, miss); 20 | }; 21 | -------------------------------------------------------------------------------- /components/icon/Icon.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | 4 | import Icon from './Icon'; 5 | 6 | describe('Icon component', () => { 7 | it('should render the icon and the screen reader helper', () => { 8 | const name = 'account'; 9 | const alt = 'Account'; 10 | const { container } = render(); 11 | const svgNode = container.querySelector(`[xlink:href="#shape-${name}"]`); 12 | const srpNode = container.querySelector('.sr-only'); 13 | 14 | expect(svgNode).toBeDefined(); 15 | expect(srpNode).toBeDefined(); 16 | expect(srpNode.textContent).toBe(alt); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /components/sidebar/SidebarListItemButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | export interface Props extends React.ButtonHTMLAttributes { 5 | children: React.ReactNode; 6 | onFocus: () => void; 7 | } 8 | 9 | const SidebarListItemButton = ({ children, className, onFocus, ...rest }: Props) => { 10 | return ( 11 | 19 | ); 20 | }; 21 | 22 | export default SidebarListItemButton; 23 | -------------------------------------------------------------------------------- /components/table/TableCell.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface Props extends React.ThHTMLAttributes { 4 | children: React.ReactNode; 5 | type?: 'body' | 'header' | 'footer'; 6 | } 7 | 8 | /** 9 | * TableCell with type 'header' is deprecated. Please use TableHeaderCell component for header cells. 10 | */ 11 | const TableCell = ({ children, type = 'body', ...rest }: Props) => { 12 | if (type === 'header' || type === 'footer') { 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | 20 | return {children}; 21 | }; 22 | 23 | export default TableCell; 24 | -------------------------------------------------------------------------------- /containers/organization/helpers/organizationKeysHelper.ts: -------------------------------------------------------------------------------- 1 | import { CachedOrganizationKey } from 'proton-shared/lib/interfaces'; 2 | 3 | export const getOrganizationKeyInfo = (organizationKey?: CachedOrganizationKey) => { 4 | // If the member has the organization key (not the organization itself). 5 | const hasOrganizationKey = !!organizationKey?.Key.PrivateKey; 6 | return { 7 | hasOrganizationKey, 8 | // It's active if it has been successfully decrypted 9 | isOrganizationKeyActive: !!organizationKey?.privateKey, 10 | // It's inactive if it exists, but not decrypted 11 | isOrganizationKeyInactive: hasOrganizationKey && !organizationKey?.privateKey, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /hooks/useBeforeUnload.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | const useBeforeUnload = (message: string) => { 4 | useEffect(() => { 5 | if (!message) { 6 | return; 7 | } 8 | const handleUnload = (event: BeforeUnloadEvent) => { 9 | if (event) { 10 | event.preventDefault(); 11 | event.returnValue = message; 12 | } 13 | return message; 14 | }; 15 | window.addEventListener('beforeunload', handleUnload); 16 | return () => { 17 | window.removeEventListener('beforeunload', handleUnload); 18 | }; 19 | }, [message]); 20 | }; 21 | 22 | export default useBeforeUnload; 23 | -------------------------------------------------------------------------------- /components/image/QRCode.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import QRCodeReact from 'qrcode.react'; 3 | 4 | import { classnames } from '../../helpers'; 5 | 6 | interface Props extends React.ComponentPropsWithoutRef<'svg'> { 7 | size?: number; 8 | value: string; 9 | } 10 | 11 | const QRCode = React.forwardRef(({ size = 200, className, ...rest }: Props, ref) => { 12 | return ( 13 | 21 | ); 22 | }); 23 | 24 | export default QRCode; 25 | -------------------------------------------------------------------------------- /components/modal/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | interface Props extends React.ComponentPropsWithoutRef<'footer'> { 5 | children?: React.ReactNode; 6 | isColumn?: boolean; 7 | } 8 | 9 | const Footer = ({ 10 | children, 11 | isColumn, 12 | className = classnames([ 13 | 'flex flex-nowrap', 14 | isColumn ? 'flex-column' : 'flex-justify-space-between flex-align-items-center', 15 | ]), 16 | ...rest 17 | }: Props) => { 18 | return ( 19 |
    20 | {children} 21 |
    22 | ); 23 | }; 24 | 25 | export default Footer; 26 | -------------------------------------------------------------------------------- /containers/addresses/useAddressModel.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const toModel = ({ ID, Self, addresses = [] }) => { 4 | const [{ DisplayName } = {}] = addresses; 5 | return { 6 | id: ID, 7 | name: Self ? DisplayName || '' : '', // DisplayName can be null 8 | address: '', 9 | domain: '', 10 | }; 11 | }; 12 | 13 | const useAddressModel = (member) => { 14 | const initialModel = toModel(member); 15 | const [model, updateModel] = useState(initialModel); 16 | const update = (key, value) => updateModel({ ...model, [key]: value }); 17 | 18 | return { 19 | model, 20 | update, 21 | }; 22 | }; 23 | 24 | export default useAddressModel; 25 | -------------------------------------------------------------------------------- /containers/forceRefresh/Provider.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useCallback, useImperativeHandle } from 'react'; 2 | 3 | import Context, { RefreshFn } from './context'; 4 | 5 | interface Props { 6 | children: React.ReactNode; 7 | } 8 | 9 | const ForceRefreshProvider = React.forwardRef(({ children }: Props, ref) => { 10 | const [state, setState] = useState(1); 11 | 12 | const refresh = useCallback(() => setState((i) => i + 1), []); 13 | 14 | useImperativeHandle(ref, () => refresh); 15 | 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | }); 22 | 23 | export default ForceRefreshProvider; 24 | -------------------------------------------------------------------------------- /containers/members/MemberRole.js: -------------------------------------------------------------------------------- 1 | import { c } from 'ttag'; 2 | import PropTypes from 'prop-types'; 3 | import { USER_ROLES } from 'proton-shared/lib/constants'; 4 | 5 | const SUPER_ADMIN_ROLE = 'superman'; 6 | 7 | const getRolesI18N = () => ({ 8 | [USER_ROLES.ADMIN_ROLE]: c('User role').t`Admin`, 9 | [USER_ROLES.MEMBER_ROLE]: c('User role').t`Member`, 10 | [SUPER_ADMIN_ROLE]: c('User role').t`Primary admin`, 11 | }); 12 | 13 | const MemberRole = ({ member }) => { 14 | const i18n = getRolesI18N(); 15 | 16 | return i18n[member.Subscriber ? SUPER_ADMIN_ROLE : member.Role]; 17 | }; 18 | 19 | MemberRole.propTypes = { 20 | member: PropTypes.object.isRequired, 21 | }; 22 | 23 | export default MemberRole; 24 | -------------------------------------------------------------------------------- /components/input/Radio.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { classnames } from '../../helpers'; 3 | 4 | export interface Props extends React.InputHTMLAttributes { 5 | id: string; 6 | className?: string; 7 | name: string; 8 | } 9 | 10 | const Radio = ({ id, children, className = 'inline-flex', name, ...rest }: Props) => ( 11 | 19 | ); 20 | 21 | export default Radio; 22 | -------------------------------------------------------------------------------- /containers/domains/CatchAllSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { c } from 'ttag'; 3 | import { SettingsParagraph, SettingsSectionWide } from '../account'; 4 | 5 | const CatchAllSection = () => { 6 | return ( 7 | 8 | 9 | {c('Info') 10 | .t`If you have a custom domain with ProtonMail, you can set a catch-all email address that will receive messages sent to your domain but to an invalid email address (e.g., typos).`} 11 | 12 | 13 | ); 14 | }; 15 | 16 | export default CatchAllSection; 17 | -------------------------------------------------------------------------------- /containers/app/interface.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationStore } from 'proton-shared/lib/authentication/createAuthenticationStore'; 2 | import { AuthSession } from '../login/interface'; 3 | 4 | export type OnLoginCallbackArguments = AuthSession; 5 | export type ProtonLoginCallback = (data: OnLoginCallbackArguments) => void; 6 | export type OnLoginCallback = (data: OnLoginCallbackArguments) => Promise; 7 | 8 | export interface PrivateAuthenticationStore extends AuthenticationStore { 9 | UID: string; 10 | localID?: number; 11 | logout: (type?: 'soft') => void; 12 | onLogout: (cb: () => Promise) => () => void; 13 | } 14 | 15 | export interface PublicAuthenticationStore { 16 | login: ProtonLoginCallback; 17 | } 18 | -------------------------------------------------------------------------------- /containers/topBanners/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DelinquentTopBanner } from './DelinquentTopBanner'; 2 | export { default as NewVersionTopBanner } from './NewVersionTopBanner'; 3 | export { default as NewVersionTopBannerView } from './NewVersionTopBannerView'; 4 | export { default as OnlineTopBanner } from './OnlineTopBanner'; 5 | export { default as PublicTopBanners } from './PublicTopBanners'; 6 | export { default as StorageLimitTopBanner } from './StorageLimitTopBanner'; 7 | export { default as SubUserTopBanner } from './SubUserTopBanner'; 8 | export { default as TimeOutOfSyncTopBanner } from './TimeOutOfSyncTopBanner'; 9 | export { default as TopBanners } from './TopBanners'; 10 | export { default as TopBanner } from './TopBanner'; 11 | -------------------------------------------------------------------------------- /components/contacts/ContactImageField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { c } from 'ttag'; 3 | import { formatImage } from 'proton-shared/lib/helpers/image'; 4 | 5 | import { Button } from '../button'; 6 | 7 | interface Props { 8 | value: string; 9 | onChange: () => void; 10 | } 11 | 12 | const ContactImageField = ({ value, onChange }: Props) => { 13 | return ( 14 |
    15 | {value ? ( 16 | 17 | ) : ( 18 | 19 | )} 20 |
    21 | ); 22 | }; 23 | 24 | export default ContactImageField; 25 | -------------------------------------------------------------------------------- /components/sidebar/SidebarPrimaryButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Button, { ButtonProps } from '../button/Button'; 4 | import { classnames } from '../../helpers'; 5 | 6 | const SidebarPrimaryButton = ( 7 | { children, className = '', ...rest }: ButtonProps, 8 | ref: React.Ref 9 | ) => { 10 | return ( 11 | 20 | ); 21 | }; 22 | 23 | export default React.forwardRef(SidebarPrimaryButton); 24 | -------------------------------------------------------------------------------- /containers/addresses/AddressesSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { Loader } from '../../components'; 4 | import { useOrganization } from '../../hooks'; 5 | import { SettingsSectionWide } from '../account'; 6 | 7 | import Addresses from './Addresses'; 8 | 9 | interface Props { 10 | isOnlySelf?: boolean; 11 | } 12 | 13 | const AddressesSection = ({ isOnlySelf }: Props) => { 14 | const [organization, loadingOrganization] = useOrganization(); 15 | 16 | return ( 17 | 18 | {loadingOrganization ? : } 19 | 20 | ); 21 | }; 22 | 23 | export default AddressesSection; 24 | -------------------------------------------------------------------------------- /components/scroll/ScrollShadows.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface Props { 4 | children: React.ReactNode; 5 | } 6 | 7 | const ScrollShadows = ({ children }: Props) => { 8 | return ( 9 |
    10 |