Merge pull request #3530 from omnivore-app/fix/web-header-fixes
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from '../templates/homeFeed/LibraryFilterMenu'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from '../templates/navMenu/LibraryMenu'
|
||||
import { theme } from '../tokens/stitches.config'
|
||||
import { OmnivoreFullLogo } from './images/OmnivoreFullLogo'
|
||||
import { OmnivoreNameLogo } from './images/OmnivoreNameLogo'
|
||||
|
||||
@ -192,6 +192,8 @@ const textVariants = {
|
||||
fontSize: '12px',
|
||||
lineHeight: '20px',
|
||||
color: '$thTextSubtle2',
|
||||
marginBlockStart: '0',
|
||||
marginTop: '10px',
|
||||
},
|
||||
error: {
|
||||
color: '$error',
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
FLAIR_ICON_NAMES,
|
||||
} from './LibraryCardStyles'
|
||||
import { sortedLabels } from '../../../lib/labelsSort'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from '../../templates/homeFeed/LibraryFilterMenu'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from '../../templates/navMenu/LibraryMenu'
|
||||
import { LibraryHoverActions } from './LibraryHoverActions'
|
||||
import {
|
||||
useHover,
|
||||
|
||||
@ -6,7 +6,7 @@ type LoadingViewProps = {
|
||||
}
|
||||
|
||||
export function LoadingView(props: LoadingViewProps): JSX.Element {
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
return (
|
||||
<VStack
|
||||
|
||||
@ -31,9 +31,6 @@ export function SettingsHeader(props: HeaderProps): JSX.Element {
|
||||
}}
|
||||
>
|
||||
<LogoBox />
|
||||
<HStack css={{ ml: 'auto' }}>
|
||||
<PrimaryDropdown showThemeSection={true} />
|
||||
</HStack>
|
||||
</HStack>
|
||||
</nav>
|
||||
)
|
||||
|
||||
@ -2,7 +2,7 @@ import { useRouter } from 'next/router'
|
||||
import { Moon, Sun } from 'phosphor-react'
|
||||
import { ReactNode, useCallback, useState } from 'react'
|
||||
import { useGetViewerQuery } from '../../lib/networking/queries/useGetViewerQuery'
|
||||
import { currentTheme, updateTheme } from '../../lib/themeUpdater'
|
||||
import { getCurrentLocalTheme, updateTheme } from '../../lib/themeUpdater'
|
||||
import { Avatar } from '../elements/Avatar'
|
||||
import { AvatarDropdown } from '../elements/AvatarDropdown'
|
||||
import {
|
||||
@ -271,7 +271,7 @@ export const StyledToggleButton = styled('button', {
|
||||
})
|
||||
|
||||
function ThemeSection(props: PrimaryDropdownProps): JSX.Element {
|
||||
const [displayTheme, setDisplayTheme] = useState(currentTheme())
|
||||
const [displayTheme, setDisplayTheme] = useState(getCurrentLocalTheme())
|
||||
|
||||
const doUpdateTheme = useCallback(
|
||||
(newTheme: ThemeId) => {
|
||||
@ -315,7 +315,7 @@ function ThemeSection(props: PrimaryDropdownProps): JSX.Element {
|
||||
}}
|
||||
>
|
||||
<StyledToggleButton
|
||||
data-state={currentTheme() != ThemeId.Dark ? 'on' : 'off'}
|
||||
data-state={getCurrentLocalTheme() != ThemeId.Dark ? 'on' : 'off'}
|
||||
onClick={() => {
|
||||
doUpdateTheme(ThemeId.Light)
|
||||
}}
|
||||
@ -324,7 +324,7 @@ function ThemeSection(props: PrimaryDropdownProps): JSX.Element {
|
||||
<Sun size={15} color={theme.colors.thTextContrast2.toString()} />
|
||||
</StyledToggleButton>
|
||||
<StyledToggleButton
|
||||
data-state={currentTheme() == ThemeId.Dark ? 'on' : 'off'}
|
||||
data-state={getCurrentLocalTheme() == ThemeId.Dark ? 'on' : 'off'}
|
||||
onClick={() => {
|
||||
doUpdateTheme(ThemeId.Dark)
|
||||
}}
|
||||
|
||||
@ -12,6 +12,7 @@ import { deinitAnalytics, setupAnalytics } from '../../lib/analytics'
|
||||
import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts'
|
||||
import { applyStoredTheme } from '../../lib/themeUpdater'
|
||||
import { logout } from '../../lib/logout'
|
||||
import { useApplyLocalTheme } from '../../lib/hooks/useApplyLocalTheme'
|
||||
|
||||
type PrimaryLayoutProps = {
|
||||
children: ReactNode
|
||||
@ -23,7 +24,7 @@ type PrimaryLayoutProps = {
|
||||
}
|
||||
|
||||
export function PrimaryLayout(props: PrimaryLayoutProps): JSX.Element {
|
||||
applyStoredTheme(false)
|
||||
useApplyLocalTheme()
|
||||
|
||||
const { viewerData } = useGetViewerQuery()
|
||||
const router = useRouter()
|
||||
|
||||
@ -11,7 +11,7 @@ import { KeyboardShortcutListModal } from './KeyboardShortcutListModal'
|
||||
import { PageMetaData } from '../patterns/PageMetaData'
|
||||
import { DEFAULT_HEADER_HEIGHT } from './homeFeed/HeaderSpacer'
|
||||
import { logout } from '../../lib/logout'
|
||||
import { SettingsMenu } from './SettingsMenu'
|
||||
import { SettingsMenu } from './navMenu/SettingsMenu'
|
||||
|
||||
type SettingsLayoutProps = {
|
||||
title?: string
|
||||
@ -26,7 +26,7 @@ export function SettingsLayout(props: SettingsLayoutProps): JSX.Element {
|
||||
useState(false)
|
||||
|
||||
useKeyboardShortcuts(navigationCommands(router))
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
const showLogout = useCallback(() => {
|
||||
setShowLogoutConfirmation(true)
|
||||
|
||||
@ -3,7 +3,11 @@ import { Box, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { useState, useEffect, useRef, useMemo } from 'react'
|
||||
import { currentTheme, getTheme, isDarkTheme } from '../../../lib/themeUpdater'
|
||||
import {
|
||||
getCurrentLocalTheme,
|
||||
getTheme,
|
||||
isDarkTheme,
|
||||
} from '../../../lib/themeUpdater'
|
||||
import PSPDFKit from 'pspdfkit'
|
||||
import { Instance, HighlightAnnotation, List, Annotation, Rect } from 'pspdfkit'
|
||||
import type { Highlight } from '../../../lib/networking/fragments/highlightFragment'
|
||||
@ -38,15 +42,13 @@ type EpubPatch = {
|
||||
export default function EpubContainer(props: EpubContainerProps): JSX.Element {
|
||||
const epubRef = useRef<HTMLDivElement | null>(null)
|
||||
const renditionRef = useRef<Rendition | undefined>(undefined)
|
||||
const [shareTarget, setShareTarget] = useState<Highlight | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [shareTarget, setShareTarget] =
|
||||
useState<Highlight | undefined>(undefined)
|
||||
const [touchStart, setTouchStart] = useState(0)
|
||||
const [notebookKey, setNotebookKey] = useState<string>(uuidv4())
|
||||
const [noteTarget, setNoteTarget] = useState<Highlight | undefined>(undefined)
|
||||
const [noteTargetPageIndex, setNoteTargetPageIndex] = useState<
|
||||
number | undefined
|
||||
>(undefined)
|
||||
const [noteTargetPageIndex, setNoteTargetPageIndex] =
|
||||
useState<number | undefined>(undefined)
|
||||
const highlightsRef = useRef<Highlight[]>([])
|
||||
|
||||
const book = useMemo(() => {
|
||||
@ -102,7 +104,7 @@ export default function EpubContainer(props: EpubContainerProps): JSX.Element {
|
||||
}
|
||||
})
|
||||
|
||||
const themeId = currentTheme()
|
||||
const themeId = getCurrentLocalTheme()
|
||||
if (themeId) {
|
||||
const readerTheme = getTheme(themeId)
|
||||
renditionRef.current.themes.override(
|
||||
|
||||
@ -13,10 +13,17 @@ import { TickedRangeSlider } from '../../elements/TickedRangeSlider'
|
||||
import { showSuccessToast } from '../../../lib/toastHelpers'
|
||||
import { ReaderSettings } from '../../../lib/hooks/useReaderSettings'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { currentThemeName, updateTheme } from '../../../lib/themeUpdater'
|
||||
import {
|
||||
getCurrentLocalTheme,
|
||||
currentThemeName,
|
||||
updateTheme,
|
||||
} from '../../../lib/themeUpdater'
|
||||
import { LineHeightIncreaseIcon } from '../../elements/images/LineHeightIncreaseIconProps'
|
||||
import { LineHeightDecreaseIcon } from '../../elements/images/LineHeightDecreaseIcon'
|
||||
import * as Switch from '@radix-ui/react-switch'
|
||||
import { Checkbox } from '@radix-ui/react-checkbox'
|
||||
import { useCurrentTheme } from '../../../lib/hooks/useCurrentTheme'
|
||||
import { useDarkModeListener } from '../../../lib/hooks/useDarkModeListener'
|
||||
|
||||
type ReaderSettingsProps = {
|
||||
readerSettings: ReaderSettings
|
||||
@ -564,7 +571,9 @@ function LayoutControls(props: LayoutControlsProps): JSX.Element {
|
||||
}
|
||||
|
||||
function ThemeSelector(): JSX.Element {
|
||||
const [currentTheme, setCurrentTheme] = useState(currentThemeName())
|
||||
const isDarkMode = useDarkModeListener()
|
||||
const { currentTheme, setCurrentTheme, resetSystemTheme } = useCurrentTheme()
|
||||
|
||||
return (
|
||||
<VStack
|
||||
css={{
|
||||
@ -575,7 +584,50 @@ function ThemeSelector(): JSX.Element {
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<StyledText style="displaySettingsLabel">Themes</StyledText>
|
||||
<HStack
|
||||
distribution="start"
|
||||
css={{
|
||||
width: '100%',
|
||||
}}
|
||||
>
|
||||
<StyledText style="displaySettingsLabel">Themes</StyledText>
|
||||
<HStack
|
||||
alignment="center"
|
||||
distribution="center"
|
||||
css={{ ml: 'auto', gap: '5px', mt: '10px', cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
console.log('clicked use system')
|
||||
updateTheme(ThemeId.System)
|
||||
}}
|
||||
>
|
||||
<Label
|
||||
htmlFor="auto-checkbox"
|
||||
css={{
|
||||
fontFamily: '$display',
|
||||
fontWeight: '500',
|
||||
fontSize: '12px',
|
||||
lineHeight: '20px',
|
||||
color: '$thTextSubtle2',
|
||||
}}
|
||||
>
|
||||
Auto
|
||||
</Label>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="auto-checkbox"
|
||||
checked={currentTheme == ThemeId.System}
|
||||
onChange={(event) => {
|
||||
if (event.target.checked) {
|
||||
setCurrentTheme(ThemeId.System)
|
||||
} else {
|
||||
resetSystemTheme()
|
||||
}
|
||||
event.stopPropagation()
|
||||
}}
|
||||
></input>
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
<HStack
|
||||
distribution="start"
|
||||
css={{
|
||||
@ -605,8 +657,7 @@ function ThemeSelector(): JSX.Element {
|
||||
}}
|
||||
data-state={currentTheme == ThemeId.Light ? 'selected' : 'unselected'}
|
||||
onClick={() => {
|
||||
updateTheme(ThemeId.Light)
|
||||
setCurrentTheme(currentThemeName())
|
||||
setCurrentTheme(ThemeId.Light)
|
||||
}}
|
||||
>
|
||||
{currentTheme == ThemeId.Light && (
|
||||
@ -635,8 +686,7 @@ function ThemeSelector(): JSX.Element {
|
||||
}}
|
||||
data-state={currentTheme == ThemeId.Dark ? 'selected' : 'unselected'}
|
||||
onClick={() => {
|
||||
updateTheme(ThemeId.Dark)
|
||||
setCurrentTheme(currentThemeName())
|
||||
setCurrentTheme(ThemeId.Dark)
|
||||
}}
|
||||
>
|
||||
{currentTheme == ThemeId.Dark && <Check color="#F9D354" size={20} />}
|
||||
@ -663,8 +713,7 @@ function ThemeSelector(): JSX.Element {
|
||||
}}
|
||||
data-state={currentTheme == ThemeId.Sepia ? 'selected' : 'unselected'}
|
||||
onClick={() => {
|
||||
updateTheme(ThemeId.Sepia)
|
||||
setCurrentTheme(currentThemeName())
|
||||
setCurrentTheme(ThemeId.Sepia)
|
||||
}}
|
||||
>
|
||||
{currentTheme == ThemeId.Sepia && <Check color="#6A6968" size={20} />}
|
||||
@ -693,8 +742,7 @@ function ThemeSelector(): JSX.Element {
|
||||
currentTheme == ThemeId.Apollo ? 'selected' : 'unselected'
|
||||
}
|
||||
onClick={() => {
|
||||
updateTheme(ThemeId.Apollo)
|
||||
setCurrentTheme(currentThemeName())
|
||||
setCurrentTheme(ThemeId.Apollo)
|
||||
}}
|
||||
>
|
||||
{currentTheme == ThemeId.Apollo && (
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Box } from '../../elements/LayoutPrimitives'
|
||||
import { useMemo } from 'react'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from './LibraryFilterMenu'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from '../navMenu/LibraryMenu'
|
||||
import { LayoutType } from './HomeFeedContainer'
|
||||
import { SuggestionBox, SuggestionAction } from '../../elements/SuggestionBox'
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ import { AddLinkModal } from './AddLinkModal'
|
||||
import { EditLibraryItemModal } from './EditItemModals'
|
||||
import { EmptyLibrary } from './EmptyLibrary'
|
||||
import { HighlightItemsLayout } from './HighlightsLayout'
|
||||
import { LibraryFilterMenu } from './LibraryFilterMenu'
|
||||
import { LibraryFilterMenu } from '../navMenu/LibraryMenu'
|
||||
import {
|
||||
LibraryHeader,
|
||||
MultiSelectMode,
|
||||
@ -96,13 +96,11 @@ export function HomeFeedContainer(): JSX.Element {
|
||||
|
||||
const gridContainerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [labelsTarget, setLabelsTarget] = useState<LibraryItem | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [labelsTarget, setLabelsTarget] =
|
||||
useState<LibraryItem | undefined>(undefined)
|
||||
|
||||
const [notebookTarget, setNotebookTarget] = useState<LibraryItem | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [notebookTarget, setNotebookTarget] =
|
||||
useState<LibraryItem | undefined>(undefined)
|
||||
|
||||
const [showAddLinkModal, setShowAddLinkModal] = useState(false)
|
||||
const [showEditTitleModal, setShowEditTitleModal] = useState(false)
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
import { LayoutType } from './HomeFeedContainer'
|
||||
import { OmnivoreSmallLogo } from '../../elements/images/OmnivoreNameLogo'
|
||||
import { DEFAULT_HEADER_HEIGHT, HeaderSpacer } from './HeaderSpacer'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from '../../templates/homeFeed/LibraryFilterMenu'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from '../navMenu/LibraryMenu'
|
||||
import { CardCheckbox } from '../../patterns/LibraryCards/LibraryCardStyles'
|
||||
import { Dropdown, DropdownOption } from '../../elements/DropdownElements'
|
||||
import { BulkAction } from '../../../lib/networking/mutations/bulkActionMutation'
|
||||
@ -36,7 +36,6 @@ import { HeaderCheckboxIcon } from '../../elements/icons/HeaderCheckboxIcon'
|
||||
import { HeaderSearchIcon } from '../../elements/icons/HeaderSearchIcon'
|
||||
import { HeaderToggleGridIcon } from '../../elements/icons/HeaderToggleGridIcon'
|
||||
import { HeaderToggleListIcon } from '../../elements/icons/HeaderToggleListIcon'
|
||||
import useWindowDimensions from '../../../lib/hooks/useGetWindowDimensions'
|
||||
|
||||
export type MultiSelectMode = 'off' | 'none' | 'some' | 'visible' | 'search'
|
||||
|
||||
@ -124,20 +123,6 @@ export function LibraryHeader(props: LibraryHeaderProps): JSX.Element {
|
||||
}
|
||||
|
||||
function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element {
|
||||
const dimensions = useWindowDimensions()
|
||||
const [showSearchBar, setShowSearchBar] = useState(false)
|
||||
const [pinnedSearches, setPinnedSearches] = usePersistedState<
|
||||
PinnedSearch[] | null
|
||||
>({
|
||||
key: `--library-pinned-searches`,
|
||||
initialValue: [],
|
||||
isSessionStorage: false,
|
||||
})
|
||||
|
||||
const isWideWindow = useMemo(() => {
|
||||
return dimensions.width >= 480
|
||||
}, [dimensions])
|
||||
|
||||
return (
|
||||
<HStack
|
||||
alignment="center"
|
||||
@ -153,78 +138,81 @@ function LargeHeaderLayout(props: LibraryHeaderProps): JSX.Element {
|
||||
<MultiSelectControls {...props} />
|
||||
</HStack>
|
||||
) : (
|
||||
<>
|
||||
{(!showSearchBar || isWideWindow) && (
|
||||
<>
|
||||
<SpanBox
|
||||
css={{
|
||||
display: 'none',
|
||||
'@mdDown': { display: 'flex' },
|
||||
}}
|
||||
>
|
||||
<MenuHeaderButton {...props} />
|
||||
</SpanBox>
|
||||
<Button
|
||||
title="Select multiple"
|
||||
style="plainIcon"
|
||||
css={{ display: 'flex', '&:hover': { opacity: '1.0' } }}
|
||||
onClick={(e) => {
|
||||
props.setMultiSelectMode('visible')
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<HeaderCheckboxIcon />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{showSearchBar ? (
|
||||
<SearchBox {...props} setShowSearchBar={setShowSearchBar} />
|
||||
) : (
|
||||
<Button
|
||||
title="search"
|
||||
style="plainIcon"
|
||||
css={{ display: 'flex', '&:hover': { opacity: '1.0' } }}
|
||||
onClick={(e) => {
|
||||
setShowSearchBar(true)
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<HeaderSearchIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
title={
|
||||
props.layout == 'GRID_LAYOUT'
|
||||
? 'Switch to list layout'
|
||||
: 'Switch to grid layout'
|
||||
}
|
||||
style="plainIcon"
|
||||
css={{
|
||||
display: 'flex',
|
||||
marginLeft: 'auto',
|
||||
'&:hover': { opacity: '1.0' },
|
||||
}}
|
||||
onClick={(e) => {
|
||||
props.updateLayout(
|
||||
props.layout == 'GRID_LAYOUT' ? 'LIST_LAYOUT' : 'GRID_LAYOUT'
|
||||
)
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
{props.layout == 'LIST_LAYOUT' ? (
|
||||
<HeaderToggleGridIcon />
|
||||
) : (
|
||||
<HeaderToggleListIcon />
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
<HeaderControls {...props} />
|
||||
)}
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
|
||||
const HeaderControls = (props: LibraryHeaderProps): JSX.Element => {
|
||||
const [showSearchBar, setShowSearchBar] = useState(false)
|
||||
return (
|
||||
<>
|
||||
<SpanBox
|
||||
css={{
|
||||
display: 'none',
|
||||
'@mdDown': { display: 'flex' },
|
||||
}}
|
||||
>
|
||||
<MenuHeaderButton {...props} />
|
||||
</SpanBox>
|
||||
<Button
|
||||
title="Select multiple"
|
||||
style="plainIcon"
|
||||
css={{ display: 'flex', '&:hover': { opacity: '1.0' } }}
|
||||
onClick={(e) => {
|
||||
props.setMultiSelectMode('visible')
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<HeaderCheckboxIcon />
|
||||
</Button>
|
||||
|
||||
{showSearchBar ? (
|
||||
<SearchBox {...props} setShowSearchBar={setShowSearchBar} />
|
||||
) : (
|
||||
<Button
|
||||
title="search"
|
||||
style="plainIcon"
|
||||
css={{ display: 'flex', '&:hover': { opacity: '1.0' } }}
|
||||
onClick={(e) => {
|
||||
setShowSearchBar(true)
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<HeaderSearchIcon />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
title={
|
||||
props.layout == 'GRID_LAYOUT'
|
||||
? 'Switch to list layout'
|
||||
: 'Switch to grid layout'
|
||||
}
|
||||
style="plainIcon"
|
||||
css={{
|
||||
display: 'flex',
|
||||
marginLeft: 'auto',
|
||||
'&:hover': { opacity: '1.0' },
|
||||
}}
|
||||
onClick={(e) => {
|
||||
props.updateLayout(
|
||||
props.layout == 'GRID_LAYOUT' ? 'LIST_LAYOUT' : 'GRID_LAYOUT'
|
||||
)
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
{props.layout == 'LIST_LAYOUT' ? (
|
||||
<HeaderToggleGridIcon />
|
||||
) : (
|
||||
<HeaderToggleListIcon />
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
type MenuHeaderButtonProps = {
|
||||
showFilterMenu: boolean
|
||||
setShowFilterMenu: (show: boolean) => void
|
||||
|
||||
50
packages/web/components/templates/navMenu/Footer.tsx
Normal file
50
packages/web/components/templates/navMenu/Footer.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { HStack, SpanBox } from '../../elements/LayoutPrimitives'
|
||||
import { SplitButton } from '../../elements/SplitButton'
|
||||
import { PrimaryDropdown } from '../PrimaryDropdown'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from './LibraryMenu'
|
||||
|
||||
type NavMenuFooterProps = {
|
||||
setShowAddLinkModal?: (show: true) => void
|
||||
}
|
||||
|
||||
export const NavMenuFooter = (props: NavMenuFooterProps): JSX.Element => {
|
||||
return (
|
||||
<HStack
|
||||
css={{
|
||||
gap: '10px',
|
||||
height: '65px',
|
||||
position: 'fixed',
|
||||
bottom: '0%',
|
||||
alignItems: 'center',
|
||||
|
||||
backgroundColor: '$thBackground2',
|
||||
width: LIBRARY_LEFT_MENU_WIDTH,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
'@mdDown': {
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PrimaryDropdown showThemeSection={true} />
|
||||
<SpanBox
|
||||
css={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: '15px',
|
||||
}}
|
||||
>
|
||||
{props.setShowAddLinkModal && (
|
||||
<SplitButton
|
||||
title="Add"
|
||||
setShowLinkMode={() => {
|
||||
props.setShowAddLinkModal && props.setShowAddLinkModal(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</SpanBox>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
@ -22,6 +22,7 @@ import { ToggleCaretRightIcon } from '../../elements/icons/ToggleCaretRightIcon'
|
||||
import { SplitButton } from '../../elements/SplitButton'
|
||||
import { AvatarDropdown } from '../../elements/AvatarDropdown'
|
||||
import { PrimaryDropdown } from '../PrimaryDropdown'
|
||||
import { NavMenuFooter } from './Footer'
|
||||
|
||||
export const LIBRARY_LEFT_MENU_WIDTH = '275px'
|
||||
|
||||
@ -122,7 +123,7 @@ export function LibraryFilterMenu(props: LibraryFilterMenuProps): JSX.Element {
|
||||
<SavedSearches {...props} savedSearches={savedSearches} />
|
||||
<Subscriptions {...props} subscriptions={subscriptions} />
|
||||
<Labels {...props} labels={labels} />
|
||||
<Footer {...props} />
|
||||
<NavMenuFooter {...props} />
|
||||
<Box css={{ height: '250px ' }} />
|
||||
</Box>
|
||||
{/* This spacer pushes library content to the right of
|
||||
@ -610,41 +611,3 @@ function EditButton(props: EditButtonProps): JSX.Element {
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
const Footer = (props: LibraryFilterMenuProps): JSX.Element => {
|
||||
return (
|
||||
<HStack
|
||||
css={{
|
||||
gap: '10px',
|
||||
height: '65px',
|
||||
position: 'fixed',
|
||||
bottom: '0%',
|
||||
alignItems: 'center',
|
||||
|
||||
backgroundColor: '$thBackground2',
|
||||
width: LIBRARY_LEFT_MENU_WIDTH,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
'&::-webkit-scrollbar': {
|
||||
display: 'none',
|
||||
},
|
||||
'@mdDown': {
|
||||
width: '100%',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PrimaryDropdown showThemeSection={true} />
|
||||
<SpanBox
|
||||
css={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: '15px',
|
||||
}}
|
||||
>
|
||||
<SplitButton
|
||||
title="Add"
|
||||
setShowLinkMode={() => props.setShowAddLinkModal(true)}
|
||||
/>
|
||||
</SpanBox>
|
||||
</HStack>
|
||||
)
|
||||
}
|
||||
@ -1,12 +1,13 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Box, HStack, SpanBox, VStack } from '../elements/LayoutPrimitives'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from './homeFeed/LibraryFilterMenu'
|
||||
import { LogoBox } from '../elements/LogoBox'
|
||||
import { Box, HStack, SpanBox, VStack } from '../../elements/LayoutPrimitives'
|
||||
import { LIBRARY_LEFT_MENU_WIDTH } from './LibraryMenu'
|
||||
import { LogoBox } from '../../elements/LogoBox'
|
||||
import Link from 'next/link'
|
||||
import { styled, theme } from '../tokens/stitches.config'
|
||||
import { Button } from '../elements/Button'
|
||||
import { styled, theme } from '../../tokens/stitches.config'
|
||||
import { Button } from '../../elements/Button'
|
||||
import { ArrowSquareUpRight } from 'phosphor-react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { NavMenuFooter } from './Footer'
|
||||
|
||||
const HorizontalDivider = styled(SpanBox, {
|
||||
width: '100%',
|
||||
@ -173,6 +174,7 @@ export function SettingsMenu(): JSX.Element {
|
||||
destination="https://docs.omnivore.app"
|
||||
title="Documentation"
|
||||
/>
|
||||
<NavMenuFooter />
|
||||
</VStack>
|
||||
</Box>
|
||||
{/* This spacer pushes library content to the right of
|
||||
@ -7,6 +7,7 @@ export enum ThemeId {
|
||||
Sepia = 'Sepia',
|
||||
Apollo = 'Apollo',
|
||||
Black = 'Black',
|
||||
System = 'System',
|
||||
}
|
||||
|
||||
export const { styled, css, theme, getCssText, globalCss, keyframes, config } =
|
||||
|
||||
11
packages/web/lib/hooks/useApplyLocalTheme.tsx
Normal file
11
packages/web/lib/hooks/useApplyLocalTheme.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { useEffect } from 'react'
|
||||
import { applyStoredTheme } from '../themeUpdater'
|
||||
import { useDarkModeListener } from './useDarkModeListener'
|
||||
|
||||
export function useApplyLocalTheme() {
|
||||
const isDark = useDarkModeListener()
|
||||
|
||||
useEffect(() => {
|
||||
applyStoredTheme()
|
||||
}, [isDark])
|
||||
}
|
||||
97
packages/web/lib/hooks/useCurrentTheme.tsx
Normal file
97
packages/web/lib/hooks/useCurrentTheme.tsx
Normal file
@ -0,0 +1,97 @@
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { usePersistedState } from './usePersistedState'
|
||||
import { getCurrentLocalTheme, updateThemeLocally } from '../themeUpdater'
|
||||
import { useDarkModeListener } from './useDarkModeListener'
|
||||
|
||||
const themeKey = 'currentTheme'
|
||||
const preferredDarkThemeKey = 'preferredDarkThemeKey'
|
||||
const preferredLightThemeKey = 'preferredLightThemeKey'
|
||||
|
||||
export function useCurrentTheme() {
|
||||
const isDarkMode = useDarkModeListener()
|
||||
const [currentThemeInternal, setCurrentThemeInternal] = usePersistedState<
|
||||
string | undefined
|
||||
>({
|
||||
key: themeKey,
|
||||
initialValue: getCurrentLocalTheme(),
|
||||
})
|
||||
|
||||
const [preferredLightTheme, setPreferredLightTheme] =
|
||||
usePersistedState<string>({
|
||||
key: preferredLightThemeKey,
|
||||
initialValue: 'Light',
|
||||
})
|
||||
|
||||
const [preferredDarkTheme, setPreferredDarkTheme] = usePersistedState<string>(
|
||||
{
|
||||
key: preferredDarkThemeKey,
|
||||
initialValue: 'Dark',
|
||||
}
|
||||
)
|
||||
|
||||
const isDarkTheme = (themeId: string): boolean => {
|
||||
if (
|
||||
themeId === 'Dark' ||
|
||||
themeId === 'Darker' ||
|
||||
themeId === 'Apollo' ||
|
||||
themeId == 'Black'
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isLightTheme = (themeId: string): boolean => {
|
||||
if (themeId === 'Sepia' || themeId == 'Light') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const currentTheme = useMemo(() => {
|
||||
return currentThemeInternal
|
||||
}, [currentThemeInternal])
|
||||
|
||||
const setCurrentTheme = useCallback(
|
||||
(themeId: string) => {
|
||||
if (isDarkTheme(themeId)) {
|
||||
setPreferredDarkTheme(themeId)
|
||||
}
|
||||
if (isLightTheme(themeId)) {
|
||||
setPreferredLightTheme(themeId)
|
||||
}
|
||||
if (themeId == 'System') {
|
||||
const current = currentThemeInternal
|
||||
if (current && isDarkTheme(current)) {
|
||||
setPreferredDarkTheme(current)
|
||||
}
|
||||
if (current && isLightTheme(current)) {
|
||||
setPreferredLightTheme(current)
|
||||
}
|
||||
}
|
||||
setCurrentThemeInternal(themeId)
|
||||
updateThemeLocally(themeId)
|
||||
},
|
||||
[setCurrentThemeInternal]
|
||||
)
|
||||
|
||||
// This is used when the user disables "System" theme
|
||||
const resetSystemTheme = useCallback(() => {
|
||||
if (isDarkMode) {
|
||||
setCurrentThemeInternal(preferredDarkTheme ?? 'Dark')
|
||||
} else {
|
||||
setCurrentThemeInternal(preferredLightTheme ?? 'Light')
|
||||
}
|
||||
}, [
|
||||
isDarkMode,
|
||||
preferredDarkTheme,
|
||||
preferredLightTheme,
|
||||
setCurrentThemeInternal,
|
||||
])
|
||||
|
||||
return {
|
||||
currentTheme,
|
||||
setCurrentTheme,
|
||||
resetSystemTheme,
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,8 @@ export default function useWindowDimensions() {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setWindowDimensions(getWindowDimensions())
|
||||
|
||||
function handleResize() {
|
||||
setWindowDimensions(getWindowDimensions())
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ export type ReaderSettings = {
|
||||
}
|
||||
|
||||
export const useReaderSettings = (): ReaderSettings => {
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
const [, updateState] = useState({})
|
||||
|
||||
|
||||
@ -8,6 +8,8 @@ import {
|
||||
} from '../components/tokens/stitches.config'
|
||||
|
||||
const themeKey = 'theme'
|
||||
const preferredDarkThemeKey = 'preferredDarkThemeKey'
|
||||
const preferredLightThemeKey = 'preferredLightThemeKey'
|
||||
|
||||
// Map legacy theme names to their new equivelents
|
||||
const LEGACY_THEMES: { [string: string]: string } = {
|
||||
@ -25,8 +27,36 @@ export function updateTheme(themeId: string): void {
|
||||
updateThemeLocally(themeId)
|
||||
}
|
||||
|
||||
const visibleThemeId = (themeId: string) => {
|
||||
if (themeId == ThemeId.System) {
|
||||
if (typeof window !== 'undefined' && window.matchMedia) {
|
||||
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
try {
|
||||
const preferred = window.localStorage.getItem(preferredDarkThemeKey)
|
||||
if (preferred) {
|
||||
return JSON.parse(preferred)
|
||||
}
|
||||
} catch {}
|
||||
return ThemeId.Dark
|
||||
} else {
|
||||
try {
|
||||
const preferred = window.localStorage.getItem(preferredLightThemeKey)
|
||||
if (preferred) {
|
||||
return JSON.parse(preferred)
|
||||
}
|
||||
} catch {}
|
||||
return ThemeId.Light
|
||||
}
|
||||
}
|
||||
return ThemeId.Light
|
||||
} else {
|
||||
return themeId
|
||||
}
|
||||
}
|
||||
|
||||
export function getTheme(themeId: string) {
|
||||
switch (themeId) {
|
||||
const themeToUse = visibleThemeId(themeId)
|
||||
switch (themeToUse) {
|
||||
case ThemeId.Dark:
|
||||
return darkTheme
|
||||
case ThemeId.Sepia:
|
||||
@ -56,7 +86,7 @@ export function updateThemeLocally(themeId: string): void {
|
||||
}
|
||||
|
||||
export function currentThemeName(): string {
|
||||
switch (currentTheme()) {
|
||||
switch (getCurrentLocalTheme()) {
|
||||
case ThemeId.Light:
|
||||
return 'Light'
|
||||
case ThemeId.Dark:
|
||||
@ -71,7 +101,7 @@ export function currentThemeName(): string {
|
||||
return 'Light'
|
||||
}
|
||||
|
||||
export function currentTheme(): ThemeId | undefined {
|
||||
export function getCurrentLocalTheme(): ThemeId | undefined {
|
||||
if (typeof window === 'undefined') {
|
||||
return undefined
|
||||
}
|
||||
@ -88,7 +118,7 @@ export function currentTheme(): ThemeId | undefined {
|
||||
return ThemeId.Light
|
||||
}
|
||||
|
||||
export function applyStoredTheme(syncWithServer = true): ThemeId | undefined {
|
||||
export function applyStoredTheme(): ThemeId | undefined {
|
||||
if (typeof window === 'undefined') {
|
||||
return undefined
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ export default function Debug(): JSX.Element {
|
||||
includeFriendsHighlights: false,
|
||||
})
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
const sortedAttributes = useMemo(() => {
|
||||
// if (!sortedAttributes) {
|
||||
|
||||
@ -20,7 +20,7 @@ export default function ArticleSavingRequestPage(): JSX.Element {
|
||||
const readerSettings = useReaderSettings()
|
||||
const [articleId, setArticleId] = useState<string | undefined>(undefined)
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
|
||||
@ -25,13 +25,12 @@ type AppArticleEmbedContentProps = {
|
||||
}
|
||||
|
||||
export default function AppArticleEmbed(): JSX.Element {
|
||||
applyStoredTheme(false) // false to skip server sync
|
||||
applyStoredTheme()
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const [contentProps, setContentProps] = useState<
|
||||
AppArticleEmbedContentProps | undefined
|
||||
>(undefined)
|
||||
const [contentProps, setContentProps] =
|
||||
useState<AppArticleEmbedContentProps | undefined>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
|
||||
@ -9,7 +9,7 @@ import { PrimaryLayout } from '../../../../components/templates/PrimaryLayout'
|
||||
import { applyStoredTheme } from '../../../../lib/themeUpdater'
|
||||
|
||||
export default function LinkRequestPage(): JSX.Element {
|
||||
applyStoredTheme(false) // false to skip server sync
|
||||
applyStoredTheme()
|
||||
|
||||
const router = useRouter()
|
||||
const [requestID, setRequestID] = useState<string | undefined>(undefined)
|
||||
|
||||
@ -2,6 +2,6 @@ import { PrivacyPolicy } from '../../components/templates/PrivacyPolicy'
|
||||
import { applyStoredTheme } from '../../lib/themeUpdater'
|
||||
|
||||
export default function Privacy(): JSX.Element {
|
||||
applyStoredTheme(false) // false to skip server sync
|
||||
applyStoredTheme()
|
||||
return <PrivacyPolicy isAppEmbed />
|
||||
}
|
||||
|
||||
@ -2,6 +2,6 @@ import { TermsAndConditions } from '../../components/templates/TermsAndCondition
|
||||
import { applyStoredTheme } from '../../lib/themeUpdater'
|
||||
|
||||
export default function Terms(): JSX.Element {
|
||||
applyStoredTheme(false) // false to skip server sync
|
||||
applyStoredTheme()
|
||||
return <TermsAndConditions isAppEmbed />
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ export default function ArticleSavingRequestPage(): JSX.Element {
|
||||
const readerSettings = useReaderSettings()
|
||||
const [url, setUrl] = useState<string | undefined>(undefined)
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
|
||||
@ -20,7 +20,7 @@ export default function ArticleSavingRequestPage(): JSX.Element {
|
||||
const readerSettings = useReaderSettings()
|
||||
const [articleId, setArticleId] = useState<string | undefined>(undefined)
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
|
||||
@ -198,7 +198,7 @@ export default function Account(): JSX.Element {
|
||||
})()
|
||||
}, [email])
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
return (
|
||||
<SettingsLayout>
|
||||
|
||||
@ -28,7 +28,7 @@ export default function Api(): JSX.Element {
|
||||
const neverExpiresDate = new Date(8640000000000000)
|
||||
const defaultExpiresAt = 'Never'
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
async function onDelete(id: string): Promise<void> {
|
||||
const result = await revokeApiKeyMutation(id)
|
||||
|
||||
@ -20,7 +20,7 @@ export default function DeleteMyAccount(): JSX.Element {
|
||||
const viewer = useGetViewerQuery()
|
||||
const [showConfirm, setShowConfirm] = useState(false)
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
async function deleteAccount(): Promise<void> {
|
||||
const viewerId = viewer.viewerData?.me?.id
|
||||
|
||||
@ -81,11 +81,10 @@ function CopyTextButton(props: CopyTextButtonProps): JSX.Element {
|
||||
export default function EmailsPage(): JSX.Element {
|
||||
const { emailAddresses, revalidate, isValidating } =
|
||||
useGetNewsletterEmailsQuery()
|
||||
const [confirmDeleteEmailId, setConfirmDeleteEmailId] = useState<
|
||||
undefined | string
|
||||
>(undefined)
|
||||
const [confirmDeleteEmailId, setConfirmDeleteEmailId] =
|
||||
useState<undefined | string>(undefined)
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
async function createEmail(): Promise<void> {
|
||||
const email = await createNewsletterEmailMutation()
|
||||
@ -114,143 +113,148 @@ export default function EmailsPage(): JSX.Element {
|
||||
return emailAddresses.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
||||
}, [emailAddresses])
|
||||
|
||||
return <>
|
||||
<SettingsTable
|
||||
pageId="settings-emails-tag"
|
||||
pageInfoLink="https://docs.omnivore.app/using/inbox.html"
|
||||
headerTitle="Address"
|
||||
createTitle="Create a new email address"
|
||||
createAction={createEmail}
|
||||
suggestionInfo={{
|
||||
title: 'Subscribe to newsletters with an Omnivore Email Address',
|
||||
message:
|
||||
'Create an Omnivore email address and use it to subscribe to newsletters or send yourself documents. Newsletters and documents will be categorized and added to your library when we receive a message. View all received emails with the "Recently Received Emails" link at the bottom of this page.',
|
||||
docs: 'https://docs.omnivore.app/using/inbox.html',
|
||||
key: '--settings-emails-show-help',
|
||||
CTAText: 'Create an email address',
|
||||
onClickCTA: () => {
|
||||
createEmail()
|
||||
},
|
||||
}}
|
||||
>
|
||||
{sortedEmailAddresses.length > 0 ? (
|
||||
sortedEmailAddresses.map((email, i) => {
|
||||
return (
|
||||
<SettingsTableRow
|
||||
key={email.address}
|
||||
title={email.address}
|
||||
isLast={i === sortedEmailAddresses.length - 1}
|
||||
onDelete={() => setConfirmDeleteEmailId(email.id)}
|
||||
deleteTitle="Delete"
|
||||
sublineElement={
|
||||
<StyledText
|
||||
css={{
|
||||
my: '5px',
|
||||
fontSize: '11px',
|
||||
a: {
|
||||
color: '$omnivoreCtaYellow',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{`created ${formattedShortDate(email.createdAt)}, `}
|
||||
<Link href="/settings/subscriptions" legacyBehavior>{`${email.subscriptionCount} subscriptions`}</Link>
|
||||
</StyledText>
|
||||
}
|
||||
titleElement={
|
||||
<CopyTextBtnWrapper
|
||||
css={{
|
||||
marginLeft: '20px',
|
||||
'@mdDown': {
|
||||
marginRight: '10px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CopyTextButton
|
||||
text={email.address}
|
||||
type={TextType.EmailAddress}
|
||||
/>
|
||||
</CopyTextBtnWrapper>
|
||||
}
|
||||
extraElement={
|
||||
email.confirmationCode ? (
|
||||
<HStack
|
||||
alignment="start"
|
||||
distribution="center"
|
||||
css={{
|
||||
width: '100%',
|
||||
backgroundColor: '$grayBgActive',
|
||||
borderRadius: '6px',
|
||||
padding: '4px 4px 4px 0px',
|
||||
'@md': {
|
||||
width: '30%',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<StyledText
|
||||
css={{
|
||||
fontSize: '11px',
|
||||
'@md': {
|
||||
marginTop: '5px',
|
||||
},
|
||||
'@mdDown': {
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
marginRight: '10px',
|
||||
}}
|
||||
>
|
||||
{`Gmail: ${email.confirmationCode}`}
|
||||
</StyledText>
|
||||
<Box>
|
||||
<CopyTextBtnWrapper>
|
||||
<CopyTextButton
|
||||
text={email.confirmationCode || ''}
|
||||
type={TextType.ConfirmationCode}
|
||||
/>
|
||||
</CopyTextBtnWrapper>
|
||||
</Box>
|
||||
</>
|
||||
</HStack>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<EmptySettingsRow
|
||||
text={isValidating ? '-' : 'No Email Addresses Found'}
|
||||
/>
|
||||
)}
|
||||
<SpanBox
|
||||
css={{
|
||||
pt: '15px',
|
||||
fontSize: '12px',
|
||||
marginLeft: 'auto',
|
||||
a: {
|
||||
color: '$omnivoreCtaYellow',
|
||||
return (
|
||||
<>
|
||||
<SettingsTable
|
||||
pageId="settings-emails-tag"
|
||||
pageInfoLink="https://docs.omnivore.app/using/inbox.html"
|
||||
headerTitle="Address"
|
||||
createTitle="Create a new email address"
|
||||
createAction={createEmail}
|
||||
suggestionInfo={{
|
||||
title: 'Subscribe to newsletters with an Omnivore Email Address',
|
||||
message:
|
||||
'Create an Omnivore email address and use it to subscribe to newsletters or send yourself documents. Newsletters and documents will be categorized and added to your library when we receive a message. View all received emails with the "Recently Received Emails" link at the bottom of this page.',
|
||||
docs: 'https://docs.omnivore.app/using/inbox.html',
|
||||
key: '--settings-emails-show-help',
|
||||
CTAText: 'Create an email address',
|
||||
onClickCTA: () => {
|
||||
createEmail()
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Link href="/settings/emails/recent">
|
||||
View recently received emails
|
||||
</Link>
|
||||
</SpanBox>
|
||||
</SettingsTable>
|
||||
{sortedEmailAddresses.length > 0 ? (
|
||||
sortedEmailAddresses.map((email, i) => {
|
||||
return (
|
||||
<SettingsTableRow
|
||||
key={email.address}
|
||||
title={email.address}
|
||||
isLast={i === sortedEmailAddresses.length - 1}
|
||||
onDelete={() => setConfirmDeleteEmailId(email.id)}
|
||||
deleteTitle="Delete"
|
||||
sublineElement={
|
||||
<StyledText
|
||||
css={{
|
||||
my: '5px',
|
||||
fontSize: '11px',
|
||||
a: {
|
||||
color: '$omnivoreCtaYellow',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{`created ${formattedShortDate(email.createdAt)}, `}
|
||||
<Link
|
||||
href="/settings/subscriptions"
|
||||
legacyBehavior
|
||||
>{`${email.subscriptionCount} subscriptions`}</Link>
|
||||
</StyledText>
|
||||
}
|
||||
titleElement={
|
||||
<CopyTextBtnWrapper
|
||||
css={{
|
||||
marginLeft: '20px',
|
||||
'@mdDown': {
|
||||
marginRight: '10px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CopyTextButton
|
||||
text={email.address}
|
||||
type={TextType.EmailAddress}
|
||||
/>
|
||||
</CopyTextBtnWrapper>
|
||||
}
|
||||
extraElement={
|
||||
email.confirmationCode ? (
|
||||
<HStack
|
||||
alignment="start"
|
||||
distribution="center"
|
||||
css={{
|
||||
width: '100%',
|
||||
backgroundColor: '$grayBgActive',
|
||||
borderRadius: '6px',
|
||||
padding: '4px 4px 4px 0px',
|
||||
'@md': {
|
||||
width: '30%',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<StyledText
|
||||
css={{
|
||||
fontSize: '11px',
|
||||
'@md': {
|
||||
marginTop: '5px',
|
||||
},
|
||||
'@mdDown': {
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
marginRight: '10px',
|
||||
}}
|
||||
>
|
||||
{`Gmail: ${email.confirmationCode}`}
|
||||
</StyledText>
|
||||
<Box>
|
||||
<CopyTextBtnWrapper>
|
||||
<CopyTextButton
|
||||
text={email.confirmationCode || ''}
|
||||
type={TextType.ConfirmationCode}
|
||||
/>
|
||||
</CopyTextBtnWrapper>
|
||||
</Box>
|
||||
</>
|
||||
</HStack>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<EmptySettingsRow
|
||||
text={isValidating ? '-' : 'No Email Addresses Found'}
|
||||
/>
|
||||
)}
|
||||
<SpanBox
|
||||
css={{
|
||||
pt: '15px',
|
||||
fontSize: '12px',
|
||||
marginLeft: 'auto',
|
||||
a: {
|
||||
color: '$omnivoreCtaYellow',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Link href="/settings/emails/recent">
|
||||
View recently received emails
|
||||
</Link>
|
||||
</SpanBox>
|
||||
</SettingsTable>
|
||||
|
||||
{confirmDeleteEmailId ? (
|
||||
<ConfirmationModal
|
||||
message={
|
||||
'Are you sure? You will stop receiving emails sent to this address.'
|
||||
}
|
||||
onAccept={async () => {
|
||||
await deleteEmail(confirmDeleteEmailId)
|
||||
setConfirmDeleteEmailId(undefined)
|
||||
}}
|
||||
onOpenChange={() => setConfirmDeleteEmailId(undefined)}
|
||||
/>
|
||||
) : null}
|
||||
</>;
|
||||
{confirmDeleteEmailId ? (
|
||||
<ConfirmationModal
|
||||
message={
|
||||
'Are you sure? You will stop receiving emails sent to this address.'
|
||||
}
|
||||
onAccept={async () => {
|
||||
await deleteEmail(confirmDeleteEmailId)
|
||||
setConfirmDeleteEmailId(undefined)
|
||||
}}
|
||||
onOpenChange={() => setConfirmDeleteEmailId(undefined)}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -152,15 +152,13 @@ const ViewRecentEmailModal = (
|
||||
|
||||
export default function RecentEmails(): JSX.Element {
|
||||
const { recentEmails, revalidate, isValidating } = useGetRecentEmailsQuery()
|
||||
const [viewingEmailText, setViewingEmailText] = useState<
|
||||
RecentEmail | undefined
|
||||
>(undefined)
|
||||
const [viewingEmailText, setViewingEmailText] =
|
||||
useState<RecentEmail | undefined>(undefined)
|
||||
|
||||
const [viewingEmailHtml, setViewingEmailHtml] = useState<
|
||||
RecentEmail | undefined
|
||||
>(undefined)
|
||||
const [viewingEmailHtml, setViewingEmailHtml] =
|
||||
useState<RecentEmail | undefined>(undefined)
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
const sortedRecentEmails = useMemo(() => {
|
||||
return recentEmails.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
|
||||
|
||||
@ -99,7 +99,7 @@ export default function Rss(): JSX.Element {
|
||||
revalidate()
|
||||
}
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
return (
|
||||
<SettingsTable
|
||||
|
||||
@ -74,7 +74,7 @@ type integrationsCard = {
|
||||
}
|
||||
}
|
||||
export default function Integrations(): JSX.Element {
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
const { integrations, revalidate } = useGetIntegrationsQuery()
|
||||
const { webhooks } = useGetWebhooksQuery()
|
||||
|
||||
|
||||
@ -153,16 +153,15 @@ export default function LabelsPage(): JSX.Element {
|
||||
const [descriptionInputText, setDescriptionInputText] = useState<string>('')
|
||||
const [isCreateMode, setIsCreateMode] = useState<boolean>(false)
|
||||
const [windowWidth, setWindowWidth] = useState<number>(0)
|
||||
const [confirmRemoveLabelId, setConfirmRemoveLabelId] = useState<
|
||||
string | null
|
||||
>(null)
|
||||
const [confirmRemoveLabelId, setConfirmRemoveLabelId] =
|
||||
useState<string | null>(null)
|
||||
const [showLabelPageHelp, setShowLabelPageHelp] = usePersistedState<boolean>({
|
||||
key: `--settings-labels-show-help`,
|
||||
initialValue: true,
|
||||
})
|
||||
const breakpoint = 768
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
const sortedLabels = useMemo(() => {
|
||||
return labels.sort((left: Label, right: Label) =>
|
||||
|
||||
@ -139,7 +139,7 @@ export default function PinnedSearches(): JSX.Element {
|
||||
dispatchList({ type: 'RESET' })
|
||||
}, [])
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
return (
|
||||
<SettingsLayout>
|
||||
|
||||
@ -149,9 +149,8 @@ const CreateActionModal = (props: CreateActionModalProps): JSX.Element => {
|
||||
}
|
||||
}
|
||||
|
||||
const [actionType, setActionType] = useState<RuleActionType | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [actionType, setActionType] =
|
||||
useState<RuleActionType | undefined>(undefined)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -219,9 +218,8 @@ export default function Rules(): JSX.Element {
|
||||
const { rules, revalidate } = useGetRulesQuery()
|
||||
const { labels } = useGetLabelsQuery()
|
||||
const [isCreateRuleModalOpen, setIsCreateRuleModalOpen] = useState(false)
|
||||
const [createActionRule, setCreateActionRule] = useState<Rule | undefined>(
|
||||
undefined
|
||||
)
|
||||
const [createActionRule, setCreateActionRule] =
|
||||
useState<Rule | undefined>(undefined)
|
||||
|
||||
const dataSource = useMemo(() => {
|
||||
return rules.map((rule: Rule) => {
|
||||
@ -235,7 +233,7 @@ export default function Rules(): JSX.Element {
|
||||
})
|
||||
}, [rules])
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
const deleteRule = useCallback(
|
||||
async (rule: Rule) => {
|
||||
|
||||
@ -156,19 +156,19 @@ export default function SavedSearchesPage(): JSX.Element {
|
||||
const [editingId, setEditingId] = useState<string | null>(null)
|
||||
const [isCreateMode, setIsCreateMode] = useState<boolean>(false)
|
||||
const [windowWidth, setWindowWidth] = useState<number>(0)
|
||||
const [confirmRemoveSavedSearchId, setConfirmRemoveSavedSearchId] = useState<
|
||||
string | null
|
||||
>(null)
|
||||
const [confirmRemoveSavedSearchId, setConfirmRemoveSavedSearchId] =
|
||||
useState<string | null>(null)
|
||||
const [draggedElementId, setDraggedElementId] = useState<string | null>(null)
|
||||
const [draggedElementPosition, setDraggedElementPosition] = useState<{
|
||||
x: number
|
||||
y: number
|
||||
} | null>(null)
|
||||
const [draggedElementPosition, setDraggedElementPosition] =
|
||||
useState<{
|
||||
x: number
|
||||
y: number
|
||||
} | null>(null)
|
||||
const [sortedSavedSearch, setSortedSavedSearch] = useState<SavedSearch[]>([])
|
||||
|
||||
// Some theming stuff here.
|
||||
const breakpoint = 768
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
useEffect(() => {
|
||||
setSortedSavedSearch(
|
||||
@ -579,14 +579,15 @@ function GenericTableCard(
|
||||
editingId === savedSearch?.id || (isCreateMode && !savedSearch)
|
||||
const iconColor = isDarkTheme() ? '#D8D7D5' : '#5F5E58'
|
||||
const DEFAULT_STYLE = { position: null }
|
||||
const [style, setStyle] = useState<
|
||||
Partial<{
|
||||
position: string | null
|
||||
top: string
|
||||
left: string
|
||||
maxWidth: string
|
||||
}>
|
||||
>(DEFAULT_STYLE)
|
||||
const [style, setStyle] =
|
||||
useState<
|
||||
Partial<{
|
||||
position: string | null
|
||||
top: string
|
||||
left: string
|
||||
maxWidth: string
|
||||
}>
|
||||
>(DEFAULT_STYLE)
|
||||
const handleEdit = () => {
|
||||
editingId && updateSavedSearch(editingId)
|
||||
setEditingId(null)
|
||||
|
||||
@ -22,7 +22,7 @@ export default function SubscriptionsPage(): JSX.Element {
|
||||
const [confirmUnsubscribeSubscription, setConfirmUnsubscribeSubscription] =
|
||||
useState<Subscription | null>(null)
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
async function onUnsubscribe(subscription: Subscription): Promise<void> {
|
||||
const result = await unsubscribeMutation(subscription.name, subscription.id)
|
||||
@ -86,7 +86,8 @@ export default function SubscriptionsPage(): JSX.Element {
|
||||
at{' '}
|
||||
<Link
|
||||
href={`/settings/emails?address=${subscription.newsletterEmail}`}
|
||||
legacyBehavior>
|
||||
legacyBehavior
|
||||
>
|
||||
{subscription.newsletterEmail}
|
||||
</Link>
|
||||
</>
|
||||
@ -101,7 +102,7 @@ export default function SubscriptionsPage(): JSX.Element {
|
||||
</StyledText>
|
||||
}
|
||||
/>
|
||||
);
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<EmptySettingsRow
|
||||
@ -125,5 +126,5 @@ export default function SubscriptionsPage(): JSX.Element {
|
||||
) : null}
|
||||
</>
|
||||
</SettingsTable>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ export default function Webhooks(): JSX.Element {
|
||||
return rows
|
||||
}, [webhooks])
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
function validateEventTypes(eventTypes: WebhookEvent[]): boolean {
|
||||
if (eventTypes.length > 0) return true
|
||||
|
||||
@ -25,7 +25,7 @@ type RunningState = 'none' | 'confirming' | 'running' | 'completed'
|
||||
export default function BulkPerformer(): JSX.Element {
|
||||
const router = useRouter()
|
||||
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
const [action, setAction] = useState<BulkAction | undefined>()
|
||||
const [query, setQuery] = useState<string>('in:all')
|
||||
|
||||
@ -17,7 +17,7 @@ import { validateCsvFile } from '../../../utils/csvValidator'
|
||||
type UploadState = 'none' | 'uploading' | 'completed'
|
||||
|
||||
export default function ImportUploader(): JSX.Element {
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>()
|
||||
const [file, setFile] = useState<File>()
|
||||
|
||||
@ -20,7 +20,7 @@ import { Tray } from 'phosphor-react'
|
||||
type UploadState = 'none' | 'uploading' | 'completed'
|
||||
|
||||
export default function ImportUploader(): JSX.Element {
|
||||
applyStoredTheme(false)
|
||||
applyStoredTheme()
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>()
|
||||
const [uploadState, setUploadState] = useState<UploadState>('none')
|
||||
|
||||
Reference in New Issue
Block a user