11""" Little X - Frontend declarations."""
22
33import from "@jac /runtime " { jacSignup , jacLogin , jacLogout , jacIsLoggedIn }
4- sv import from .server { setup_profile , get_profile , get_all_profiles , follow_user , unfollow_user , create_tweet , delete_tweet , like_tweet , add_comment , load_feed , get_trending }
5- import from "lucide -react " { Home , Compass , User , LogOut , Search , Feather }
4+ sv import from .server { setup_profile , get_profile , get_all_profiles , follow_user , unfollow_user , create_tweet , delete_tweet , like_tweet , add_comment , load_feed , get_trending , create_channel , join_channel , leave_channel , get_channels , get_channel_detail , create_channel_tweet }
5+ import from "lucide -react " { Home , Compass , User , LogOut , Search , Feather , Hash , Users , Plus , ArrowLeft }
66import from .components .TweetCard { TweetCard }
77import from .components .AuthForm { AuthForm }
88import "./global .css ";
@@ -26,7 +26,15 @@ def:pub app() -> Any {
2626 bioText : str = " " ,
2727 trendingTags : list = [],
2828 notifCount : int = 0 ,
29- showWelcome : bool = False ;
29+ showWelcome : bool = False ,
30+ channels : list = [],
31+ channelsLoading : bool = False ,
32+ selectedChannel : Any = None ,
33+ channelPosts : list = [],
34+ channelComposerText : str = " " ,
35+ showCreateChannel : bool = False ,
36+ newChannelName : str = " " ,
37+ newChannelDescription : str = " " ;
3038
3139 can with entry {
3240 isLoggedIn = jacIsLoggedIn();
@@ -54,6 +62,13 @@ def:pub app() -> Any {
5462 async def handleSearch(query: str ) - > None ;
5563 def handleRepost (tweetId : str , content : str , author : str ) -> None ;
5664 def handleDismissWelcome () -> None ;
65+ async def loadChannels() - > None ;
66+ async def handleCreateChannel() - > None ;
67+ async def handleJoinChannel(channelId: str ) - > None ;
68+ async def handleLeaveChannel(channelId: str ) - > None ;
69+ async def handleSelectChannel(channelId: str ) - > None ;
70+ async def handleChannelPost() - > None ;
71+ def handleBackToChannels () -> None ;
5772
5873 if checkingAuth {
5974 return <div className = " lx-loading" >Loading... </div >;
@@ -106,6 +121,13 @@ def:pub app() -> Any {
106121 <Compass size = { 22 } />
107122 <span >Explore </span >
108123 </div >
124+ <div
125+ className = { " lx-nav-item" + (" lx-nav-item-active" if activeTab == " channels" else " " )}
126+ onClick = { lambda - > None { activeTab = " channels" ; loadChannels(); }}
127+ >
128+ <Hash size = { 22 } />
129+ <span >Channels </span >
130+ </div >
109131 <div
110132 className = { " lx-nav-item" + (" lx-nav-item-active" if activeTab == " profile" else " " )}
111133 onClick = { lambda - > None { activeTab = " profile" ; localStorage.setItem(" lx_activity_total" , String(current_total_activity)); notifCount = 0 ; }}
@@ -140,7 +162,7 @@ def:pub app() -> Any {
140162 <main className = " lx-main" >
141163 <div className = " lx-feed-header" >
142164 <span className = " lx-feed-title" >
143- { (" Home" if activeTab == " feed" else (" Explore" if activeTab == " explore" else " Profile" ))}
165+ { (" Home" if activeTab == " feed" else (" Explore" if activeTab == " explore" else ( " Channels " if activeTab == " channels " else " Profile" ) ))}
144166 </span >
145167 { (
146168 <button
@@ -272,6 +294,179 @@ def:pub app() -> Any {
272294 </div >
273295 ) if activeTab == " explore" else None }
274296
297+ { (
298+ <div >
299+ { (
300+ <div >
301+ <div className = " lx-channel-actions" >
302+ <button
303+ className = " lx-post-btn"
304+ onClick = { lambda - > None { showCreateChannel = not showCreateChannel; }}
305+ >
306+ <Plus size = { 16 } />
307+ { " Create Channel" }
308+ </button >
309+ </div >
310+
311+ { (
312+ <div className = " lx-channel-create-form" >
313+ <input
314+ className = " lx-input"
315+ placeholder = " Channel name"
316+ value = { newChannelName}
317+ onChange = { lambda e: Any - > None { newChannelName = e.target.value; }}
318+ />
319+ <input
320+ className = " lx-input"
321+ placeholder = " Description (optional)"
322+ value = { newChannelDescription}
323+ onChange = { lambda e: Any - > None { newChannelDescription = e.target.value; }}
324+ />
325+ <div style = { {" display" : " flex" , " gap" : " 0.5rem" }} >
326+ <button
327+ className = " lx-post-btn"
328+ disabled = { not newChannelName.trim()}
329+ onClick = { lambda - > None { handleCreateChannel(); }}
330+ >
331+ Create
332+ </button >
333+ <button
334+ className = " lx-edit-profile-btn"
335+ onClick = { lambda - > None { showCreateChannel = False ; newChannelName = " " ; newChannelDescription = " " ; }}
336+ >
337+ Cancel
338+ </button >
339+ </div >
340+ </div >
341+ ) if showCreateChannel else None }
342+
343+ { (
344+ <div className = " lx-loading" >Loading channels... </div >
345+ ) if channelsLoading else None }
346+
347+ { (
348+ <div >
349+ { (
350+ <div className = " lx-empty" >
351+ <div className = " lx-empty-title" >No channels yet </div >
352+ <span >Create one to get started! </span >
353+ </div >
354+ ) if channels.length == 0 else None }
355+ { [
356+ <div
357+ className = " lx-channel-item"
358+ key = { ch[" id" ]}
359+ onClick = { lambda - > None { handleSelectChannel(ch[" id" ]); }}
360+ >
361+ <div className = " lx-channel-icon" >
362+ <Hash size = { 20 } />
363+ </div >
364+ <div className = " lx-channel-item-info" >
365+ <div className = " lx-channel-item-name" >{ ch[" name" ]} </div >
366+ { (
367+ <div className = " lx-channel-item-desc" >{ ch[" description" ]} </div >
368+ ) if ch[" description" ] else None }
369+ <div className = " lx-channel-item-meta" >
370+ <Users size = { 12 } />
371+ <span >{ String(ch[" member_count" ]) + " members" } </span >
372+ </div >
373+ </div >
374+ { (
375+ <span className = " lx-channel-joined-badge" >Joined </span >
376+ ) if ch[" is_member" ] else None }
377+ </div >
378+ for ch in channels
379+ ]}
380+ </div >
381+ ) if not channelsLoading else None }
382+ </div >
383+ ) if not selectedChannel else (
384+ <div >
385+ <div className = " lx-channel-header" >
386+ <button
387+ className = " lx-edit-profile-btn"
388+ onClick = { lambda - > None { handleBackToChannels(); }}
389+ style = { {" display" : " flex" , " alignItems" : " center" , " gap" : " 0.3rem" }}
390+ >
391+ <ArrowLeft size = { 16 } />
392+ Back
393+ </button >
394+ <div className = " lx-channel-name" >
395+ <Hash size = { 20 } />
396+ { selectedChannel.name}
397+ </div >
398+ { (
399+ <div className = " lx-channel-desc" >{ selectedChannel.description} </div >
400+ ) if selectedChannel.description else None }
401+ <div className = " lx-channel-meta" >
402+ <Users size = { 14 } />
403+ <span >{ String(selectedChannel.member_count) + " members" } </span >
404+ { (
405+ <button
406+ className = " lx-edit-profile-btn"
407+ onClick = { lambda - > None { handleLeaveChannel(selectedChannel.id); }}
408+ >
409+ Leave
410+ </button >
411+ ) if selectedChannel.is_member else (
412+ <button
413+ className = " lx-follow-btn"
414+ onClick = { lambda - > None { handleJoinChannel(selectedChannel.id); }}
415+ >
416+ Join
417+ </button >
418+ )}
419+ </div >
420+ </div >
421+
422+ { (
423+ <div className = " lx-composer" >
424+ <img className = " lx-avatar" src = { profileAvatar} alt = { profileUsername} />
425+ <div className = " lx-composer-body" >
426+ <textarea
427+ className = " lx-composer-textarea"
428+ placeholder = { " Post in #" + selectedChannel.name}
429+ value = { channelComposerText}
430+ onChange = { lambda e: Any - > None { channelComposerText = e.target.value; }}
431+ rows = { 2 }
432+ />
433+ <div className = " lx-composer-footer" >
434+ <div />
435+ <button
436+ className = " lx-post-btn"
437+ disabled = { not channelComposerText.trim()}
438+ onClick = { lambda - > None { handleChannelPost(); }}
439+ >
440+ Post
441+ </button >
442+ </div >
443+ </div >
444+ </div >
445+ ) if selectedChannel.is_member else None }
446+
447+ { (
448+ <div className = " lx-empty" >
449+ <div className = " lx-empty-title" >No posts yet </div >
450+ <span >{ (" Be the first to post!" if selectedChannel.is_member else " Join this channel to post!" )} </span >
451+ </div >
452+ ) if channelPosts.length == 0 else None }
453+
454+ { [
455+ <TweetCard
456+ key = { tweet[" id" ]}
457+ tweet = { tweet}
458+ myUsername = { profileUsername}
459+ onLike = { handleLike}
460+ onComment = { handleComment}
461+ onDelete = { handleDelete}
462+ onRepost = { handleRepost}
463+ /> for tweet in channelPosts
464+ ]}
465+ </div >
466+ )}
467+ </div >
468+ ) if activeTab == " channels" else None }
469+
275470 { (
276471 <div >
277472 <div className = " lx-profile-banner" />
@@ -429,6 +624,12 @@ def:pub app() -> Any {
429624 >
430625 <Compass size = { 24 } />
431626 </div >
627+ <div
628+ className = { " lx-mobile-nav-item" + (" lx-mobile-nav-item-active" if activeTab == " channels" else " " )}
629+ onClick = { lambda - > None { activeTab = " channels" ; loadChannels(); }}
630+ >
631+ <Hash size = { 24 } />
632+ </div >
432633 <div
433634 className = { " lx-mobile-nav-item" + (" lx-mobile-nav-item-active" if activeTab == " profile" else " " )}
434635 onClick = { lambda - > None { activeTab = " profile" ; localStorage.setItem(" lx_activity_total" , String(current_total_activity)); notifCount = 0 ; }}
0 commit comments