import type { Folder } from '@nylas-labs/cli-kit/v3'
import { useQuery } from '@tanstack/react-query'
import { createFileRoute, Link, Outlet, useNavigate, useRouterState } from '@tanstack/react-router'
import { Menu, Pencil } from 'lucide-react'
import { type ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { AppRailLogo, AppRailMobileNav, AppRailNav, type MailboxAccountOption } from '#app/components/AppRail'
import { CommandPalette, useCommandPaletteShortcut } from '#app/components/CommandPalette'
import { MobileTabBar } from '#app/components/MobileTabBar'
import {
CHROME_ROW_CLASS,
CHROME_ROW_SHELL_CLASS,
MAIL_HEADER_GRID_CLASS,
MAIL_SIDEBAR_WIDTH_CLASS,
} from '#app/config/layout'
import { mailboxInfoQueryOptions } from '#app/query/mailbox-info'
import { MailSearchBar } from '#features/mail/components/MailSearchBar'
import { MailSidebar } from '#features/mail/components/MailSidebar'
import {
activeMailSidebarFolderId,
composeSearchFromMailLocation,
liveSearchTarget,
mailSearchInputValue,
} from '#features/mail/lib/mail-ui-model'
import { foldersQueryOptions } from '#features/mail/state/mail-queries'
import { getFolders } from '#server/fns'
import { Sheet } from '#shared/components/Sheet'
import { cn } from '#shared/lib/utils'
export const Route = createFileRoute('/mail')({
loader: async ({ context }) => {
const [info, folders] = await Promise.all([
context.queryClient.ensureQueryData(mailboxInfoQueryOptions()),
context.queryClient.ensureQueryData(foldersQueryOptions(() => getFolders())),
])
return { info, folders }
},
staleTime: Number.POSITIVE_INFINITY,
component: MailLayout,
})
type MailInfo = {
email: string
displayName?: string
appName: string
accounts?: MailboxAccountOption[]
}
function MailLayout() {
const { info: initialInfo, folders: initialFolders } = Route.useLoaderData()
const { data: info } = useQuery({
...mailboxInfoQueryOptions(),
initialData: initialInfo,
initialDataUpdatedAt: 0,
})
const { data: folders } = useQuery({
...foldersQueryOptions(() => getFolders()),
initialData: initialFolders,
})
return
}
export function MailRouteScreen({
info,
folders,
defaultFolderId,
children,
}: {
info: MailInfo
folders: Folder[]
defaultFolderId?: string
children?: ReactNode
}) {
const navigate = useNavigate()
const pathname = useRouterState({ select: (state) => state.location.pathname })
const isSearchRoute = useRouterState({
select: (state) => state.matches.some((match) => match.routeId === '/mail/search'),
})
const searchParams = useRouterState({ select: (state) => state.location.search as Record })
const searchScopeFolderId = typeof searchParams.folderId === 'string' ? searchParams.folderId : undefined
const routeSearchQuery = typeof searchParams.q === 'string' ? searchParams.q : undefined
const currentFolderId = useMemo(
() => activeMailSidebarFolderId(pathname, searchScopeFolderId) ?? defaultFolderId,
[defaultFolderId, pathname, searchScopeFolderId],
)
const selectedSearchThreadId = typeof searchParams.threadId === 'string' ? searchParams.threadId : undefined
const labelBaseFolder =
typeof searchParams.baseFolderId === 'string' ? searchParams.baseFolderId : undefined
const activeSearchFolderId = currentFolderId ?? searchScopeFolderId
const composeSearch = useMemo(
() => composeSearchFromMailLocation(pathname, activeSearchFolderId, selectedSearchThreadId),
[activeSearchFolderId, pathname, selectedSearchThreadId],
)
const searchAwarePathname = isSearchRoute ? '/mail/search' : pathname
const [query, setQuery] = useState('')
const [sidebarOpen, setSidebarOpen] = useState(false)
const [paletteOpen, setPaletteOpen] = useState(false)
const openPalette = useCallback(() => setPaletteOpen(true), [])
const closePalette = useCallback(() => setPaletteOpen(false), [])
const focusSearch = useCallback(() => {
closePalette()
document.getElementById('mail-search')?.focus()
}, [closePalette])
useCommandPaletteShortcut(openPalette)
useEffect(() => {
setQuery(mailSearchInputValue(searchAwarePathname, routeSearchQuery))
}, [routeSearchQuery, searchAwarePathname])
async function navigateSearch(nextQuery: string) {
const target = liveSearchTarget(
nextQuery,
searchAwarePathname,
activeSearchFolderId,
selectedSearchThreadId,
)
if (target.kind === 'search') {
await navigate({
to: '/mail/search',
search: { q: target.q, ...(target.folderId ? { folderId: target.folderId } : {}) },
replace: true,
})
} else if (target.kind === 'thread') {
await navigate({
to: '/mail/f/$folderId/t/$threadId',
params: { folderId: target.folderId, threadId: target.threadId },
replace: true,
})
} else if (target.kind === 'folder') {
await navigate({
to: '/mail/f/$folderId',
params: { folderId: target.folderId },
replace: true,
})
}
}
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
const target = event.target as HTMLElement | null
const isTyping =
target?.tagName === 'INPUT' || target?.tagName === 'TEXTAREA' || target?.isContentEditable
if (isTyping || event.repeat || event.metaKey || event.ctrlKey || event.altKey) return
if (event.key === '/') {
event.preventDefault()
document.getElementById('mail-search')?.focus()
}
if (event.key.toLowerCase() === 'c') {
event.preventDefault()
navigate({
to: '/mail/compose',
search: composeSearch,
})
}
}
window.addEventListener('keydown', onKeyDown)
return () => window.removeEventListener('keydown', onKeyDown)
}, [composeSearch, navigate])
const sidebarProps = {
folders,
composeSearch,
currentFolderId,
baseFolderId: labelBaseFolder,
onNavigate: () => setSidebarOpen(false),
onFolderDeleted: (folderId: string) => {
if (currentFolderId === folderId) {
navigate({ to: '/mail/f/$folderId', params: { folderId: 'inbox' } })
}
},
}
const railNavProps = {
email: info.email,
displayName: info.displayName,
accounts: info.accounts,
active: 'mail' as const,
onOpenCommandPalette: openPalette,
}
return (
setSidebarOpen(false)} title="Navigation">
setSidebarOpen(false)}
showDestinations={false}
/>
)
}