Ignore:
Timestamp:
03/24/26 22:13:36 (3 months ago)
Author:
kikisrbinoska <srbinoskakristina07@…>
Branches:
main
Children:
7fbb91c
Parents:
acf690c
Message:

Fixed reading lists,comments and likes

Location:
chapterx-frontend/src/pages
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • chapterx-frontend/src/pages/admin/AdminGenresPage.tsx

    racf690c r73b69b2  
    1 import React, { useState } from 'react'
     1import React, { useState, useEffect } from 'react'
    22import { useNavigate } from 'react-router-dom'
    33import { ArrowLeft, Plus, Trash2, Tag } from 'lucide-react'
     
    1010export const AdminGenresPage: React.FC = () => {
    1111  const navigate = useNavigate()
    12   const { genres, addGenre, deleteGenre } = useStoryStore()
     12  const { genres, fetchGenres, addGenre, deleteGenre } = useStoryStore()
    1313  const { addToast } = useUIStore()
    1414  const [addOpen, setAddOpen] = useState(false)
     
    1616  const [deleteTarget, setDeleteTarget] = useState<Genre | null>(null)
    1717
    18   const handleAdd = () => {
     18  useEffect(() => { fetchGenres() }, [])
     19
     20  const handleAdd = async () => {
    1921    if (!newName.trim()) return
    2022    if (genres.some(g => g.name.toLowerCase() === newName.trim().toLowerCase())) {
     
    2224      return
    2325    }
    24     addGenre({ genre_id: Date.now(), name: newName.trim(), story_count: 0 })
    25     addToast(`Genre "${newName.trim()}" added!`)
    26     setNewName('')
    27     setAddOpen(false)
     26    try {
     27      await addGenre(newName.trim())
     28      addToast(`Genre "${newName.trim()}" added!`)
     29      setNewName('')
     30      setAddOpen(false)
     31    } catch {
     32      addToast('Failed to add genre', 'error')
     33    }
    2834  }
    2935
    30   const handleDelete = (genre: Genre) => {
    31     if (genre.story_count > 0) {
    32       addToast('Cannot delete genre with associated stories', 'error')
    33       return
     36  const handleDelete = async (genre: Genre) => {
     37    try {
     38      await deleteGenre(genre.genre_id)
     39      addToast(`Genre "${genre.name}" deleted`, 'info')
     40    } catch {
     41      addToast('Failed to delete genre', 'error')
    3442    }
    35     deleteGenre(genre.genre_id)
    36     addToast(`Genre "${genre.name}" deleted`, 'info')
    3743    setDeleteTarget(null)
    3844  }
  • chapterx-frontend/src/pages/reading-list/CommunityListsPage.tsx

    racf690c r73b69b2  
    1 import React, { useState } from 'react'
     1import React, { useState, useEffect } from 'react'
    22import { useNavigate } from 'react-router-dom'
    33import { Globe, BookOpen, User } from 'lucide-react'
     
    1010export const CommunityListsPage: React.FC = () => {
    1111  const navigate = useNavigate()
    12   const { readingLists } = useStoryStore()
     12  const { readingLists, fetchReadingLists } = useStoryStore()
    1313  const [selectedList, setSelectedList] = useState<ReadingList | null>(null)
     14
     15  useEffect(() => { fetchReadingLists() }, [])
    1416
    1517  const publicLists = readingLists.filter(l => l.is_public)
  • chapterx-frontend/src/pages/reading-list/ReadingListPage.tsx

    racf690c r73b69b2  
    1212  const navigate = useNavigate()
    1313  const { currentUser } = useAuthStore()
    14   const { readingLists, fetchReadingLists, createReadingList, deleteReadingList, removeStoryFromList } = useStoryStore()
     14  const { readingLists, fetchUserReadingLists, createReadingList, deleteReadingList, removeStoryFromList } = useStoryStore()
     15  const [fetching, setFetching] = useState(false)
     16  const [confirmRemove, setConfirmRemove] = useState<{ listId: number; storyId: number; title: string } | null>(null)
    1517
    1618  useEffect(() => {
    17     fetchReadingLists()
    18   }, [])
     19    if (!currentUser) return
     20    setFetching(true)
     21    fetchUserReadingLists(currentUser.user_id).finally(() => setFetching(false))
     22  }, [currentUser?.user_id])
    1923  const { addToast } = useUIStore()
    2024  const [createOpen, setCreateOpen] = useState(false)
     
    3337  }
    3438
    35   const myLists = readingLists.filter(l => l.user_id === currentUser.user_id)
     39  const myLists = readingLists.filter(l => l.user_id === currentUser!.user_id)
    3640
    3741  const handleCreate = async () => {
     
    7377      </div>
    7478
    75       {myLists.length === 0 ? (
     79      {fetching ? (
     80        <div className="flex flex-col items-center py-20 text-slate-500">
     81          <BookOpen size={48} className="mb-4 opacity-40 animate-pulse" />
     82          <p className="text-sm">Loading your lists...</p>
     83        </div>
     84      ) : myLists.length === 0 ? (
    7685        <div className="flex flex-col items-center py-20 text-slate-500">
    7786          <BookOpen size={48} className="mb-4 opacity-40" />
     
    124133                  <div className="space-y-2">
    125134                    {list.stories.slice(0, 4).map(item => (
    126                       <div key={item.item_id} className="flex items-center gap-3 p-2 rounded-lg hover:bg-slate-700/50 group">
    127                         <div className="flex-1 min-w-0">
     135                      <div key={item.item_id} className="flex items-center gap-2 p-2 rounded-lg hover:bg-slate-700/50">
     136                        <div className="flex-1 min-w-0 overflow-hidden">
    128137                          <button
    129138                            onClick={() => navigate(`/story/${item.story_id}`)}
    130                             className="text-white text-sm font-medium hover:text-indigo-300 transition-colors truncate block text-left"
     139                            className="text-white text-sm font-medium hover:text-indigo-300 transition-colors truncate block text-left w-full"
    131140                          >
    132141                            {item.story_title}
    133142                          </button>
    134143                          <div className="flex items-center gap-2 mt-0.5">
    135                             <span className="text-slate-500 text-xs">by {item.author_username}</span>
     144                            <span className="text-slate-500 text-xs truncate">by {item.author_username}</span>
    136145                            {item.genres.slice(0, 1).map(g => <GenreBadge key={g} genre={g} />)}
    137146                          </div>
    138147                        </div>
    139148                        <button
    140                           onClick={() => removeStoryFromList(list.list_id, item.story_id)}
    141                           className="opacity-0 group-hover:opacity-100 text-slate-500 hover:text-rose-400 transition-all"
     149                          onClick={() => setConfirmRemove({ listId: list.list_id, storyId: item.story_id, title: item.story_title })}
     150                          className="flex-shrink-0 text-slate-500 hover:text-rose-400 hover:bg-rose-400/10 transition-all p-1.5 rounded-lg"
     151                          title="Remove from list"
    142152                        >
    143                           <X size={14} />
     153                          <Trash2 size={14} />
    144154                        </button>
    145155                      </div>
     
    165175      )}
    166176
     177      {/* Confirm remove modal */}
     178      <Modal isOpen={!!confirmRemove} onClose={() => setConfirmRemove(null)} title="Remove Story">
     179        <div className="space-y-4">
     180          <p className="text-slate-300 text-sm">
     181            Are you sure you want to remove <span className="text-white font-medium">"{confirmRemove?.title}"</span> from this list?
     182          </p>
     183          <div className="flex gap-3">
     184            <Button variant="secondary" className="flex-1" onClick={() => setConfirmRemove(null)}>Cancel</Button>
     185            <Button variant="danger" className="flex-1" onClick={async () => {
     186              if (!confirmRemove) return
     187              await removeStoryFromList(confirmRemove.listId, confirmRemove.storyId)
     188              addToast('Story removed from list', 'info')
     189              setConfirmRemove(null)
     190            }}>Remove</Button>
     191          </div>
     192        </div>
     193      </Modal>
     194
    167195      {/* Create modal */}
    168196      <Modal isOpen={createOpen} onClose={() => setCreateOpen(false)} title="Create Reading List">
  • chapterx-frontend/src/pages/story/StoryDetailPage.tsx

    racf690c r73b69b2  
    2323  const [listModalOpen, setListModalOpen] = useState(false)
    2424  const [newListName, setNewListName] = useState('')
     25  const [liveLikes, setLiveLikes] = useState<number | null>(null)
     26  const [liveComments, setLiveComments] = useState<number | null>(null)
    2527
    2628  const story = stories.find(s => s.story_id === Number(id))
     
    153155          {/* Action bar */}
    154156          <div className="flex items-center gap-3 flex-wrap">
    155             <LikeButton storyId={story.story_id} authorUserId={story.user_id} totalLikes={story.total_likes} />
     157            <LikeButton storyId={story.story_id} authorUserId={story.user_id} totalLikes={story.total_likes} onCountChange={setLiveLikes} />
    156158            {currentUser && (
    157159              <Button variant="secondary" size="sm" onClick={() => setListModalOpen(true)}>
     
    183185          {/* Comments */}
    184186          <div className="border-t border-slate-700 pt-8">
    185             <CommentSection storyId={story.story_id} authorUserId={story.user_id} />
     187            <CommentSection storyId={story.story_id} authorUserId={story.user_id} onCountChange={setLiveComments} />
    186188          </div>
    187189        </div>
     
    203205              <div className="flex justify-between">
    204206                <span className="text-slate-400">Likes</span>
    205                 <span className="text-white">{story.total_likes.toLocaleString()}</span>
     207                <span className="text-white">{(liveLikes ?? story.total_likes).toLocaleString()}</span>
    206208              </div>
    207209              <div className="flex justify-between">
     
    211213              <div className="flex justify-between">
    212214                <span className="text-slate-400">Comments</span>
    213                 <span className="text-white">{story.total_comments}</span>
     215                <span className="text-white">{liveComments ?? story.total_comments}</span>
    214216              </div>
    215217              <div className="flex justify-between">
Note: See TracChangeset for help on using the changeset viewer.