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