index.tsx raw

   1  import ChannelList from '@/components/Chat/ChannelList'
   2  import ChannelView from '@/components/Chat/ChannelView'
   3  import InboxContent from '@/components/Inbox/InboxContent'
   4  import PrimaryPageLayout from '@/layouts/PrimaryPageLayout'
   5  import { cn } from '@/lib/utils'
   6  import { useDM } from '@/providers/DMProvider'
   7  import { useNostr } from '@/providers/NostrProvider'
   8  import { TPageRef } from '@/types'
   9  import { Hash, LogIn, MessageCircle, MessageSquare } from 'lucide-react'
  10  import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'
  11  import { useTranslation } from 'react-i18next'
  12  import { usePrimaryPage } from '@/PageManager'
  13  import { Button } from '@/components/ui/button'
  14  import { useChat } from '@/providers/ChatProvider'
  15  
  16  type ChatTab = 'dms' | 'channels'
  17  
  18  const ChatPage = forwardRef<TPageRef>((_, ref) => {
  19    const { t } = useTranslation()
  20    const layoutRef = useRef<TPageRef>(null)
  21    const { pubkey } = useNostr()
  22    const { navigate } = usePrimaryPage()
  23    const { markInboxAsSeen } = useDM()
  24    const [activeTab, setActiveTab] = useState<ChatTab>('dms')
  25  
  26    useImperativeHandle(ref, () => layoutRef.current as TPageRef)
  27  
  28    useEffect(() => {
  29      if (pubkey && activeTab === 'dms') {
  30        markInboxAsSeen()
  31      }
  32    }, [pubkey, activeTab, markInboxAsSeen])
  33  
  34    const { refreshChannels } = useChat()
  35  
  36    useEffect(() => {
  37      if (activeTab === 'channels') {
  38        refreshChannels()
  39      }
  40    }, [activeTab, refreshChannels])
  41  
  42    return (
  43      <PrimaryPageLayout
  44        pageName="chat"
  45        ref={layoutRef}
  46        titlebar={<ChatTitlebar activeTab={activeTab} onTabChange={setActiveTab} />}
  47      >
  48        {pubkey ? (
  49          activeTab === 'dms' ? (
  50            <InboxContent />
  51          ) : (
  52            <div className="flex h-[calc(100vh-3rem)] overflow-hidden">
  53              <div className="w-56 shrink-0 border-r overflow-hidden">
  54                <ChannelList />
  55              </div>
  56              <div className="flex-1 min-w-0 overflow-hidden">
  57                <ChannelView />
  58              </div>
  59            </div>
  60          )
  61        ) : (
  62          <div className="flex flex-col items-center justify-center h-64 gap-4 text-muted-foreground">
  63            <MessageCircle className="size-12" />
  64            <div className="text-center">
  65              <p className="font-medium">{t('Sign in to chat')}</p>
  66              <p className="text-sm">{t('Direct messages and public channels')}</p>
  67            </div>
  68            <Button onClick={() => navigate('settings')} className="gap-2">
  69              <LogIn className="size-4" />
  70              {t('Sign In')}
  71            </Button>
  72          </div>
  73        )}
  74      </PrimaryPageLayout>
  75    )
  76  })
  77  ChatPage.displayName = 'ChatPage'
  78  export default ChatPage
  79  
  80  function ChatTitlebar({
  81    activeTab,
  82    onTabChange
  83  }: {
  84    activeTab: ChatTab
  85    onTabChange: (tab: ChatTab) => void
  86  }) {
  87    const { t } = useTranslation()
  88    const { hasNewMessages } = useDM()
  89    const { hasUnreadChannels } = useChat()
  90  
  91    return (
  92      <div className="flex items-center h-full px-3 gap-1">
  93        <MessageCircle className="size-5 shrink-0" />
  94        <div className="text-lg font-semibold mr-3">{t('Chat')}</div>
  95        <button
  96          onClick={() => onTabChange('dms')}
  97          className={cn(
  98            'flex items-center gap-1.5 px-3 py-1 rounded-md text-sm font-medium transition-colors',
  99            activeTab === 'dms'
 100              ? 'bg-accent text-accent-foreground'
 101              : 'text-muted-foreground hover:text-foreground hover:bg-accent/50'
 102          )}
 103        >
 104          <MessageSquare className="size-3.5" />
 105          {t('DMs')}
 106          {hasNewMessages && (
 107            <div className="w-2 h-2 bg-primary rounded-full" />
 108          )}
 109        </button>
 110        <button
 111          onClick={() => onTabChange('channels')}
 112          className={cn(
 113            'flex items-center gap-1.5 px-3 py-1 rounded-md text-sm font-medium transition-colors',
 114            activeTab === 'channels'
 115              ? 'bg-accent text-accent-foreground'
 116              : 'text-muted-foreground hover:text-foreground hover:bg-accent/50'
 117          )}
 118        >
 119          <Hash className="size-3.5" />
 120          {t('Channels')}
 121          {hasUnreadChannels && (
 122            <div className="w-2 h-2 bg-primary rounded-full" />
 123          )}
 124        </button>
 125      </div>
 126    )
 127  }
 128