FeedActionBar.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. 'use client';
  2. import { useState } from 'react';
  3. import { Heart, MessageCircle, Share2, MoreHorizontal, Bookmark, Link as LinkIcon, Flag } from 'lucide-react';
  4. import { fetchApi } from '@/lib/utils/client';
  5. import useAuth from '@/hooks/useAuth';
  6. import {
  7. DropdownMenu,
  8. DropdownMenuContent,
  9. DropdownMenuItem,
  10. DropdownMenuTrigger
  11. } from '@/components/ui/dropdown-menu';
  12. type Props = {
  13. postID: number;
  14. initialLikes: number;
  15. initialComments: number;
  16. initialBookmarks: number;
  17. };
  18. export default function FeedActionBar({ postID, initialLikes, initialComments }: Props) {
  19. const { loginCheck } = useAuth();
  20. const [likes, setLikes] = useState(initialLikes);
  21. const [liked, setLiked] = useState(false);
  22. const [bookmarked, setBookmarked] = useState(false);
  23. const [busy, setBusy] = useState(false);
  24. const handleLike = async () => {
  25. if (!loginCheck() || busy) {
  26. return;
  27. }
  28. setBusy(true);
  29. try {
  30. const res = await fetchApi<{ hasLike: boolean; likes: number }>(`/api/feed/post/${postID}/like`, {
  31. method: 'POST',
  32. silent: true
  33. });
  34. if (res.success && res.data) {
  35. setLiked(res.data.hasLike);
  36. setLikes(res.data.likes);
  37. }
  38. } catch (err) {
  39. console.error(err);
  40. } finally {
  41. setBusy(false);
  42. }
  43. };
  44. const handleBookmark = async () => {
  45. if (!loginCheck() || busy) {
  46. return;
  47. }
  48. setBusy(true);
  49. try {
  50. const res = await fetchApi<{ hasBookmark: boolean; bookmarks: number }>(`/api/feed/post/${postID}/bookmark`, {
  51. method: 'POST',
  52. silent: true
  53. });
  54. if (res.success && res.data) {
  55. setBookmarked(res.data.hasBookmark);
  56. }
  57. } catch (err) {
  58. console.error(err);
  59. } finally {
  60. setBusy(false);
  61. }
  62. };
  63. const handleShare = async () => {
  64. const url = `${window.location.origin}/feed/post/${postID}`;
  65. try {
  66. await navigator.clipboard.writeText(url);
  67. alert('링크가 복사되었습니다.');
  68. } catch {
  69. prompt('링크를 복사하세요:', url);
  70. }
  71. };
  72. return (
  73. <div className="feed__card-actions">
  74. <button type="button" className={`feed__action${liked ? ' feed__action--active' : ''}`} onClick={handleLike} aria-pressed={liked} aria-label="좋아요">
  75. <span className="feed__action-icon">
  76. <Heart size={18} fill={liked ? 'currentColor' : 'none'} />
  77. </span>
  78. <span className="feed__action-count">{likes}</span>
  79. </button>
  80. <a href={`/feed/post/${postID}`} className="feed__action" aria-label="답글">
  81. <span className="feed__action-icon">
  82. <MessageCircle size={18} />
  83. </span>
  84. <span className="feed__action-count">{initialComments}</span>
  85. </a>
  86. <button type="button" className="feed__action" onClick={handleShare} aria-label="공유">
  87. <span className="feed__action-icon">
  88. <Share2 size={18} />
  89. </span>
  90. </button>
  91. <DropdownMenu>
  92. <DropdownMenuTrigger asChild>
  93. <button type="button" className="feed__action" aria-label="더보기">
  94. <span className="feed__action-icon">
  95. <MoreHorizontal size={18} />
  96. </span>
  97. </button>
  98. </DropdownMenuTrigger>
  99. <DropdownMenuContent align="end">
  100. <DropdownMenuItem onClick={handleBookmark}>
  101. <Bookmark size={14} fill={bookmarked ? 'currentColor' : 'none'} className="mr-2" />
  102. {bookmarked ? '저장 해제' : '저장'}
  103. </DropdownMenuItem>
  104. <DropdownMenuItem onClick={handleShare}>
  105. <LinkIcon size={14} className="mr-2" />
  106. 링크 복사
  107. </DropdownMenuItem>
  108. <DropdownMenuItem>
  109. <Flag size={14} className="mr-2" />
  110. 신고
  111. </DropdownMenuItem>
  112. </DropdownMenuContent>
  113. </DropdownMenu>
  114. </div>
  115. );
  116. }