chore: refactored post selecting on mobile
24ebb2d0
1 file(s) · +186 −34
| 1 | 1 | "use client"; |
|
| 2 | 2 | ||
| 3 | 3 | import * as React from "react"; |
|
| 4 | - | import { Plus, RotateCw, MoreVertical, Check, X } from "lucide-react"; |
|
| 4 | + | import { |
|
| 5 | + | Plus, |
|
| 6 | + | RotateCw, |
|
| 7 | + | MoreVertical, |
|
| 8 | + | Check, |
|
| 9 | + | X, |
|
| 10 | + | ChevronLeft, |
|
| 11 | + | } from "lucide-react"; |
|
| 5 | 12 | ||
| 6 | 13 | import { NavUser } from "@/components/nav-user"; |
|
| 7 | 14 | import { NavFeeds } from "@/components/nav-feeds"; |
|
| 82 | 89 | const [searchQuery, setSearchQuery] = React.useState(""); |
|
| 83 | 90 | const [isAddingFeed, setIsAddingFeed] = React.useState(false); |
|
| 84 | 91 | const [statusMessage, setStatusMessage] = React.useState(""); |
|
| 92 | + | // Mobile navigation state: 'feeds' or 'posts' |
|
| 93 | + | const [mobileView, setMobileView] = React.useState<"feeds" | "posts">( |
|
| 94 | + | "feeds", |
|
| 95 | + | ); |
|
| 85 | 96 | ||
| 86 | - | const { hidden } = useSidebar(); |
|
| 97 | + | const { hidden, isMobile, setOpenMobile } = useSidebar(); |
|
| 87 | 98 | const { insert, update } = useEvolu(); |
|
| 88 | 99 | const allFeeds = useQuery(allFeedsQuery); |
|
| 89 | 100 | const allReadStatuses = useQuery(allReadStatusesQuery); |
|
| 120 | 131 | [allReadStatuses], |
|
| 121 | 132 | ); |
|
| 122 | 133 | ||
| 134 | + | // Handle feed selection - on mobile, navigate to posts view |
|
| 135 | + | const handleFeedSelect = React.useCallback( |
|
| 136 | + | (feedId: string | null) => { |
|
| 137 | + | onFeedSelect(feedId); |
|
| 138 | + | // Navigate to posts view on mobile for any feed selection (including "All Feeds") |
|
| 139 | + | if (isMobile) { |
|
| 140 | + | setMobileView("posts"); |
|
| 141 | + | } |
|
| 142 | + | }, |
|
| 143 | + | [onFeedSelect, isMobile], |
|
| 144 | + | ); |
|
| 145 | + | ||
| 146 | + | // Handle back to feeds on mobile |
|
| 147 | + | const handleBackToFeeds = React.useCallback(() => { |
|
| 148 | + | setMobileView("feeds"); |
|
| 149 | + | onFeedSelect(null); |
|
| 150 | + | }, [onFeedSelect]); |
|
| 151 | + | ||
| 123 | 152 | // Handle post selection and mark as read |
|
| 124 | 153 | const handlePostSelect = React.useCallback( |
|
| 125 | 154 | (postId: string) => { |
|
| 146 | 175 | ||
| 147 | 176 | // Call the original onPostSelect |
|
| 148 | 177 | onPostSelect(postId); |
|
| 178 | + | ||
| 179 | + | // On mobile, close the sidebar after selecting a post |
|
| 180 | + | if (isMobile) { |
|
| 181 | + | setOpenMobile(false); |
|
| 182 | + | // Reset to feeds view for next time sidebar opens |
|
| 183 | + | setTimeout(() => setMobileView("feeds"), 300); |
|
| 184 | + | } |
|
| 149 | 185 | }, |
|
| 150 | - | [allReadStatusesWithUnread, feedPosts, insert, update, onPostSelect], |
|
| 186 | + | [ |
|
| 187 | + | allReadStatusesWithUnread, |
|
| 188 | + | feedPosts, |
|
| 189 | + | insert, |
|
| 190 | + | update, |
|
| 191 | + | onPostSelect, |
|
| 192 | + | isMobile, |
|
| 193 | + | setOpenMobile, |
|
| 194 | + | ], |
|
| 151 | 195 | ); |
|
| 152 | 196 | ||
| 153 | 197 | // Mark all visible posts as read |
|
| 371 | 415 | <SidebarHeader> |
|
| 372 | 416 | <SidebarMenu> |
|
| 373 | 417 | <SidebarMenuItem> |
|
| 374 | - | <a href="#"> |
|
| 375 | - | <div className="grid flex-1 text-left text-xl px-2 pt-2"> |
|
| 376 | - | <span className="truncate font-bold">Alcove</span> |
|
| 418 | + | {isMobile && mobileView === "posts" ? ( |
|
| 419 | + | <div className="flex items-center gap-2 px-2 pt-2"> |
|
| 420 | + | <Button |
|
| 421 | + | variant="ghost" |
|
| 422 | + | size="sm" |
|
| 423 | + | onClick={handleBackToFeeds} |
|
| 424 | + | className="h-8 w-8 p-0" |
|
| 425 | + | > |
|
| 426 | + | <ChevronLeft className="size-5" /> |
|
| 427 | + | </Button> |
|
| 428 | + | <div className="flex-1 text-left text-xl"> |
|
| 429 | + | <span className="truncate font-bold"> |
|
| 430 | + | {selectedFeedId |
|
| 431 | + | ? allFeeds.find((f) => f.id === selectedFeedId) |
|
| 432 | + | ?.title || "Posts" |
|
| 433 | + | : "All Posts"} |
|
| 434 | + | </span> |
|
| 435 | + | </div> |
|
| 377 | 436 | </div> |
|
| 378 | - | </a> |
|
| 437 | + | ) : ( |
|
| 438 | + | <a href="#"> |
|
| 439 | + | <div className="grid flex-1 text-left text-xl px-2 pt-2"> |
|
| 440 | + | <span className="truncate font-bold">Alcove</span> |
|
| 441 | + | </div> |
|
| 442 | + | </a> |
|
| 443 | + | )} |
|
| 379 | 444 | </SidebarMenuItem> |
|
| 380 | 445 | </SidebarMenu> |
|
| 381 | 446 | </SidebarHeader> |
|
| 382 | 447 | <SidebarContent> |
|
| 383 | - | <SidebarGroup> |
|
| 384 | - | <SidebarGroupLabel>Actions</SidebarGroupLabel> |
|
| 385 | - | <SidebarGroupContent> |
|
| 386 | - | <SidebarMenu> |
|
| 387 | - | <DialogTrigger asChild> |
|
| 388 | - | <SidebarMenuItem> |
|
| 389 | - | <SidebarMenuButton> |
|
| 390 | - | <Plus className="size-4" /> |
|
| 391 | - | <span>Add Feed</span> |
|
| 392 | - | </SidebarMenuButton> |
|
| 393 | - | </SidebarMenuItem> |
|
| 394 | - | </DialogTrigger> |
|
| 395 | - | <SidebarMenuItem> |
|
| 396 | - | <SidebarMenuButton onClick={reset}> |
|
| 397 | - | <RotateCw className="size-4" /> |
|
| 398 | - | <span>Reset</span> |
|
| 399 | - | </SidebarMenuButton> |
|
| 400 | - | </SidebarMenuItem> |
|
| 401 | - | </SidebarMenu> |
|
| 402 | - | </SidebarGroupContent> |
|
| 403 | - | </SidebarGroup> |
|
| 404 | - | <NavFeeds |
|
| 405 | - | feeds={allFeeds} |
|
| 406 | - | selectedFeedId={selectedFeedId} |
|
| 407 | - | onFeedSelect={onFeedSelect} |
|
| 408 | - | /> |
|
| 448 | + | {isMobile && mobileView === "posts" ? ( |
|
| 449 | + | // Mobile posts view |
|
| 450 | + | <div className="flex flex-col h-full"> |
|
| 451 | + | <div className="gap-2 border-b p-3 flex flex-col"> |
|
| 452 | + | <div className="flex w-full items-center justify-between gap-2"> |
|
| 453 | + | <div className="text-foreground text-sm font-semibold truncate"> |
|
| 454 | + | {filteredPosts.length} post |
|
| 455 | + | {filteredPosts.length !== 1 ? "s" : ""} |
|
| 456 | + | </div> |
|
| 457 | + | <DropdownMenu> |
|
| 458 | + | <DropdownMenuTrigger asChild> |
|
| 459 | + | <Button |
|
| 460 | + | variant="ghost" |
|
| 461 | + | size="sm" |
|
| 462 | + | className="h-6 w-6 p-0" |
|
| 463 | + | > |
|
| 464 | + | <MoreVertical className="h-4 w-4" /> |
|
| 465 | + | </Button> |
|
| 466 | + | </DropdownMenuTrigger> |
|
| 467 | + | <DropdownMenuContent align="end"> |
|
| 468 | + | <DropdownMenuItem onClick={handleMarkAllAsRead}> |
|
| 469 | + | <Check className="h-4 w-4 mr-2" /> |
|
| 470 | + | Mark all as read |
|
| 471 | + | </DropdownMenuItem> |
|
| 472 | + | <DropdownMenuItem onClick={handleMarkAllAsUnread}> |
|
| 473 | + | <X className="h-4 w-4 mr-2" /> |
|
| 474 | + | Mark all as unread |
|
| 475 | + | </DropdownMenuItem> |
|
| 476 | + | </DropdownMenuContent> |
|
| 477 | + | </DropdownMenu> |
|
| 478 | + | </div> |
|
| 479 | + | <Input |
|
| 480 | + | placeholder="Search..." |
|
| 481 | + | value={searchQuery} |
|
| 482 | + | onChange={(e) => setSearchQuery(e.target.value)} |
|
| 483 | + | className="h-8" |
|
| 484 | + | /> |
|
| 485 | + | </div> |
|
| 486 | + | <div className="flex-1 overflow-y-auto"> |
|
| 487 | + | {filteredPosts.length === 0 ? ( |
|
| 488 | + | <div className="p-4 text-center text-sm text-muted-foreground"> |
|
| 489 | + | No posts found |
|
| 490 | + | </div> |
|
| 491 | + | ) : ( |
|
| 492 | + | filteredPosts.map((post) => { |
|
| 493 | + | const isRead = isPostRead(post.id); |
|
| 494 | + | return ( |
|
| 495 | + | <button |
|
| 496 | + | type="button" |
|
| 497 | + | key={post.id} |
|
| 498 | + | onClick={() => handlePostSelect(post.id)} |
|
| 499 | + | className={`hover:bg-sidebar-accent flex items-start gap-2 border-b px-3 py-3 text-sm text-left w-full last:border-b-0 transition-colors ${ |
|
| 500 | + | selectedPostId === post.id |
|
| 501 | + | ? "bg-sidebar-accent" |
|
| 502 | + | : "" |
|
| 503 | + | }`} |
|
| 504 | + | > |
|
| 505 | + | {/* Unread indicator */} |
|
| 506 | + | <div className="flex-shrink-0 pt-1"> |
|
| 507 | + | {!isRead && ( |
|
| 508 | + | <div className="size-2 rounded-full bg-primary" /> |
|
| 509 | + | )} |
|
| 510 | + | </div> |
|
| 511 | + | {/* Post content */} |
|
| 512 | + | <div className="flex flex-col gap-1.5 flex-1 min-w-0"> |
|
| 513 | + | <span className="font-medium line-clamp-2 leading-snug"> |
|
| 514 | + | {post.title} |
|
| 515 | + | </span> |
|
| 516 | + | {post.author && ( |
|
| 517 | + | <span className="text-muted-foreground text-xs"> |
|
| 518 | + | {post.author} |
|
| 519 | + | </span> |
|
| 520 | + | )} |
|
| 521 | + | </div> |
|
| 522 | + | </button> |
|
| 523 | + | ); |
|
| 524 | + | }) |
|
| 525 | + | )} |
|
| 526 | + | </div> |
|
| 527 | + | </div> |
|
| 528 | + | ) : ( |
|
| 529 | + | // Feeds view (desktop and mobile default) |
|
| 530 | + | <> |
|
| 531 | + | <SidebarGroup> |
|
| 532 | + | <SidebarGroupLabel>Actions</SidebarGroupLabel> |
|
| 533 | + | <SidebarGroupContent> |
|
| 534 | + | <SidebarMenu> |
|
| 535 | + | <DialogTrigger asChild> |
|
| 536 | + | <SidebarMenuItem> |
|
| 537 | + | <SidebarMenuButton> |
|
| 538 | + | <Plus className="size-4" /> |
|
| 539 | + | <span>Add Feed</span> |
|
| 540 | + | </SidebarMenuButton> |
|
| 541 | + | </SidebarMenuItem> |
|
| 542 | + | </DialogTrigger> |
|
| 543 | + | <SidebarMenuItem> |
|
| 544 | + | <SidebarMenuButton onClick={reset}> |
|
| 545 | + | <RotateCw className="size-4" /> |
|
| 546 | + | <span>Reset</span> |
|
| 547 | + | </SidebarMenuButton> |
|
| 548 | + | </SidebarMenuItem> |
|
| 549 | + | </SidebarMenu> |
|
| 550 | + | </SidebarGroupContent> |
|
| 551 | + | </SidebarGroup> |
|
| 552 | + | <NavFeeds |
|
| 553 | + | feeds={allFeeds} |
|
| 554 | + | selectedFeedId={selectedFeedId} |
|
| 555 | + | onFeedSelect={handleFeedSelect} |
|
| 556 | + | /> |
|
| 557 | + | </> |
|
| 558 | + | )} |
|
| 409 | 559 | </SidebarContent> |
|
| 410 | 560 | <SidebarFooter> |
|
| 411 | - | <NavUser user={data.user} /> |
|
| 561 | + | {!isMobile || mobileView === "feeds" ? ( |
|
| 562 | + | <NavUser user={data.user} /> |
|
| 563 | + | ) : null} |
|
| 412 | 564 | </SidebarFooter> |
|
| 413 | 565 | </Sidebar> |
|
| 414 | 566 | <DialogContent className="sm:max-w-[425px]"> |
|