Ignore:
Timestamp:
03/22/26 17:58:40 (4 months ago)
Author:
kikisrbinoska <srbinoskakristina07@…>
Branches:
main
Children:
73b69b2
Parents:
b62cefc
Message:

Added fixes for the login,stories management and reading lists

Location:
chapterx-frontend/src/store
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • chapterx-frontend/src/store/authStore.ts

    rb62cefc racf690c  
    2727      token: null,
    2828      showMatureContent: true,
    29       allUsers: [...mockUsers],
     29      allUsers: [],
    3030
    3131      login: async (emailOrUsername, password) => {
     
    3636            : get().allUsers.find(u => u.username === emailOrUsername)?.email || emailOrUsername
    3737          const res = await axios.post(`${API_BASE}/auth/login`, { email, password }, { timeout: 3000 })
    38           const { token, userId, username } = res.data
    39           const user = get().allUsers.find(u => u.user_id === userId) ||
    40             get().allUsers.find(u => u.username === username) || {
    41               user_id: userId,
    42               username,
    43               email,
    44               name: username,
    45               surname: '',
    46               role: 'regular' as const,
    47               created_at: new Date().toISOString(),
    48               follower_count: 0,
    49               following_count: 0,
    50             }
     38          const { token, userId, username, name, surname, role } = res.data
     39          const user: User = {
     40            user_id: userId,
     41            username,
     42            email,
     43            name: name ?? username,
     44            surname: surname ?? '',
     45            role: role ?? 'regular',
     46            created_at: new Date().toISOString(),
     47            follower_count: 0,
     48            following_count: 0,
     49          }
    5150          set({ currentUser: user, token })
    5251          return
    53         } catch {
    54           // Fall through to mock login
     52        } catch (err: any) {
     53          // Only fall through to mock if the backend is unreachable (network/timeout)
     54          // If the backend responded with an error (4xx/5xx), surface it to the user
     55          if (err?.response) {
     56            const message = err.response.data?.message || err.response.data || 'Invalid email or password.'
     57            throw new Error(typeof message === 'string' ? message : 'Invalid email or password.')
     58          }
     59          // Network error / timeout — fall through to mock login
    5560        }
    5661
     
    6671
    6772      register: async (data, role) => {
    68         // Try backend first
    6973        try {
    7074          await axios.post(`${API_BASE}/auth/register`, data, { timeout: 3000 })
    71         } catch {
    72           // Fall through to mock register
     75          // Register doesn't return a token — auto-login to get one
     76          const loginRes = await axios.post(`${API_BASE}/auth/login`, { email: data.email, password: data.password }, { timeout: 3000 })
     77          const { token, userId, username } = loginRes.data
     78          const newUser: User = {
     79            user_id: userId,
     80            username,
     81            email: data.email,
     82            name: data.name,
     83            surname: data.surname,
     84            role,
     85            created_at: new Date().toISOString(),
     86            follower_count: 0,
     87            following_count: 0,
     88          }
     89          set(state => ({ allUsers: [...state.allUsers, newUser], currentUser: newUser, token }))
     90          return
     91        } catch (err: any) {
     92          if (err?.response) {
     93            const message = err.response.data?.message || err.response.data || 'Registration failed.'
     94            throw new Error(typeof message === 'string' ? message : 'Registration failed.')
     95          }
     96          // Network error / timeout — fall through to mock register
     97          console.warn('Backend unreachable during register, using mock:', err?.message)
    7398        }
    7499
  • chapterx-frontend/src/store/storyStore.ts

    rb62cefc racf690c  
    2323} from '../data/mockData'
    2424
     25const API = 'https://localhost:7125/api'
     26
     27function getAuthHeaders() {
     28  try {
     29    const token = JSON.parse(localStorage.getItem('chapterx-auth') || '{}')?.state?.token
     30    return token ? { Authorization: `Bearer ${token}` } : {}
     31  } catch {
     32    return {}
     33  }
     34}
     35
    2536interface LikeRecord {
    2637  userId: number
     
    3849  likedStories: LikeRecord[]
    3950
     51  // Fetch from backend
     52  fetchStories: () => Promise<void>
     53  fetchChapters: () => Promise<void>
     54  fetchReadingLists: () => Promise<void>
     55
    4056  // Story actions
    41   addStory: (story: Story) => void
    42   updateStory: (id: number, partial: Partial<Story>) => void
    43   deleteStory: (id: number) => void
     57  addStory: (story: Story) => Promise<number>
     58  updateStory: (id: number, partial: Partial<Story>) => Promise<void>
     59  deleteStory: (id: number) => Promise<void>
    4460  updateStoryStatus: (id: number, status: StoryStatus) => void
    4561
    4662  // Chapter actions
    47   addChapter: (chapter: Chapter) => void
    48   updateChapter: (id: number, partial: Partial<Chapter>) => void
    49   deleteChapter: (id: number) => void
     63  addChapter: (chapter: Chapter) => Promise<void>
     64  updateChapter: (id: number, partial: Partial<Chapter>) => Promise<void>
     65  deleteChapter: (id: number) => Promise<void>
    5066  incrementViewCount: (chapterId: number) => void
    5167
     
    7490
    7591  // Reading list actions
    76   createReadingList: (list: ReadingList) => void
    77   addStoryToList: (listId: number, item: ReadingListItem) => void
    78   removeStoryFromList: (listId: number, storyId: number) => void
    79   deleteReadingList: (listId: number) => void
     92  createReadingList: (list: ReadingList) => Promise<number>
     93  addStoryToList: (listId: number, item: ReadingListItem) => Promise<void>
     94  removeStoryFromList: (listId: number, storyId: number) => Promise<void>
     95  deleteReadingList: (listId: number) => Promise<void>
    8096}
    8197
     
    90106  likedStories: [],
    91107
    92   addStory: (story) =>
    93     set(state => ({ stories: [...state.stories, story] })),
    94 
    95   updateStory: (id, partial) =>
     108  fetchStories: async () => {
     109    try {
     110      const res = await axios.get(`${API}/stories`)
     111      const data: any[] = res.data?.stories ?? res.data ?? []
     112      const stories: Story[] = data.map((s: any) => ({
     113        story_id: s.id,
     114        user_id: s.userId,
     115        title: s.shortDescription,
     116        short_description: s.shortDescription,
     117        content: s.content,
     118        mature_content: s.matureContent,
     119        status: 'published' as StoryStatus,
     120        author_username: '',
     121        created_at: s.createdAt,
     122        updated_at: s.updatedAt,
     123        total_likes: 0,
     124        total_comments: 0,
     125        total_chapters: 0,
     126        total_views: 0,
     127        genres: [],
     128      }))
     129      if (stories.length > 0) set({ stories })
     130    } catch {
     131      // keep mock data on failure
     132    }
     133  },
     134
     135  fetchChapters: async () => {
     136    try {
     137      const res = await axios.get(`${API}/chapters`)
     138      const data: any[] = res.data?.chapters ?? res.data ?? []
     139      const chapters: Chapter[] = data.map((c: any) => ({
     140        chapter_id: c.id,
     141        story_id: c.storyId,
     142        title: c.title ?? c.name,
     143        content: c.content,
     144        chapter_number: c.number,
     145        word_count: c.wordCount ?? 0,
     146        view_count: c.viewCount ?? 0,
     147        is_published: true,
     148        created_at: c.createdAt,
     149        updated_at: c.updatedAt,
     150      }))
     151      if (chapters.length > 0) set({ chapters })
     152    } catch {
     153      // keep mock data on failure
     154    }
     155  },
     156
     157  addStory: async (story) => {
     158    set(state => ({ stories: [...state.stories, story] }))
     159    const res = await axios.post(`${API}/stories`, {
     160      matureContent: story.mature_content,
     161      shortDescription: story.title || story.short_description,
     162      image: null,
     163      content: story.content,
     164      userId: story.user_id,
     165    }, { headers: getAuthHeaders() })
     166    const backendId = res.data?.id ?? res.data
     167    if (backendId && backendId !== story.story_id) {
     168      set(state => ({
     169        stories: state.stories.map(s =>
     170          s.story_id === story.story_id ? { ...s, story_id: backendId } : s
     171        ),
     172        chapters: state.chapters.map(c =>
     173          c.story_id === story.story_id ? { ...c, story_id: backendId } : c
     174        ),
     175      }))
     176      return backendId
     177    }
     178    return story.story_id
     179  },
     180
     181  updateStory: async (id, partial) => {
    96182    set(state => ({
    97183      stories: state.stories.map(s => (s.story_id === id ? { ...s, ...partial } : s)),
    98     })),
    99 
    100   deleteStory: (id) =>
     184    }))
     185    try {
     186      const story = get().stories.find(s => s.story_id === id)
     187      if (!story) return
     188      await axios.put(`${API}/stories/${id}`, {
     189        id,
     190        matureContent: partial.mature_content ?? story.mature_content,
     191        shortDescription: partial.title ?? partial.short_description ?? story.title ?? story.short_description,
     192        image: null,
     193        content: partial.content ?? story.content,
     194      }, { headers: getAuthHeaders() })
     195    } catch {
     196      // keep optimistic update on failure
     197    }
     198  },
     199
     200  deleteStory: async (id) => {
    101201    set(state => ({
    102202      stories: state.stories.filter(s => s.story_id !== id),
     
    104204      comments: state.comments.filter(c => c.story_id !== id),
    105205      collaborations: state.collaborations.filter(c => c.story_id !== id),
    106     })),
     206    }))
     207    try {
     208      await axios.delete(`${API}/stories/${id}`, { headers: getAuthHeaders() })
     209    } catch {
     210      // keep optimistic delete on failure
     211    }
     212  },
    107213
    108214  updateStoryStatus: (id, status) =>
     
    113219    })),
    114220
    115   addChapter: (chapter) =>
     221  addChapter: async (chapter) => {
    116222    set(state => ({
    117223      chapters: [...state.chapters, chapter],
     
    121227          : s
    122228      ),
    123     })),
    124 
    125   updateChapter: (id, partial) =>
     229    }))
     230    const res = await axios.post(`${API}/chapters`, {
     231      number: chapter.chapter_number,
     232      name: chapter.title,
     233      title: chapter.title,
     234      content: chapter.content,
     235      storyId: chapter.story_id,
     236    }, { headers: getAuthHeaders() })
     237    const backendId = res.data?.id ?? res.data
     238    if (backendId && backendId !== chapter.chapter_id) {
     239      set(state => ({
     240        chapters: state.chapters.map(c =>
     241          c.chapter_id === chapter.chapter_id ? { ...c, chapter_id: backendId } : c
     242        ),
     243      }))
     244    }
     245  },
     246
     247  updateChapter: async (id, partial) => {
    126248    set(state => ({
    127249      chapters: state.chapters.map(c =>
    128250        c.chapter_id === id ? { ...c, ...partial, updated_at: new Date().toISOString() } : c
    129251      ),
    130     })),
    131 
    132   deleteChapter: (id) =>
     252    }))
     253    try {
     254      const chapter = get().chapters.find(c => c.chapter_id === id)
     255      if (!chapter) return
     256      await axios.put(`${API}/chapters/${id}`, {
     257        id,
     258        number: partial.chapter_number ?? chapter.chapter_number,
     259        name: partial.title ?? chapter.title,
     260        title: partial.title ?? chapter.title,
     261        content: partial.content ?? chapter.content,
     262        wordCount: partial.word_count ?? chapter.word_count,
     263      }, { headers: getAuthHeaders() })
     264    } catch {
     265      // keep optimistic update on failure
     266    }
     267  },
     268
     269  deleteChapter: async (id) => {
    133270    set(state => {
    134271      const chapter = state.chapters.find(c => c.chapter_id === id)
     
    143280          : state.stories,
    144281      }
    145     }),
     282    })
     283    try {
     284      await axios.delete(`${API}/chapters/${id}`, { headers: getAuthHeaders() })
     285    } catch {
     286      // keep optimistic delete on failure
     287    }
     288  },
    146289
    147290  incrementViewCount: (chapterId) =>
     
    218361  fetchSuggestions: async () => {
    219362    try {
    220       const res = await axios.get('https://localhost:7125/api/aisuggestions')
     363      const res = await axios.get(`${API}/aisuggestions`)
    221364      const data = res.data.aiSuggestions ?? res.data
    222365      const mapped: AISuggestion[] = data.map((s: any) => ({
     
    248391    }))
    249392    try {
    250       await axios.put(`https://localhost:7125/api/aisuggestions/${id}`, {
     393      await axios.put(`${API}/aisuggestions/${id}`, {
    251394        id,
    252395        originalText: s.original_text,
     
    268411    }))
    269412    try {
    270       await axios.put(`https://localhost:7125/api/aisuggestions/${id}`, {
     413      await axios.put(`${API}/aisuggestions/${id}`, {
    271414        id,
    272415        originalText: s.original_text,
     
    284427    set(state => ({ aiSuggestions: [...state.aiSuggestions, { ...suggestion, suggestion_id: tempId }] }))
    285428    try {
    286       const res = await axios.post('https://localhost:7125/api/aisuggestions', {
     429      const res = await axios.post('${API}/aisuggestions', {
    287430        originalText: suggestion.original_text,
    288431        suggestedText: suggestion.suggested_text,
     
    306449    set(state => ({ genres: state.genres.filter(g => g.genre_id !== id) })),
    307450
    308   createReadingList: (list) =>
    309     set(state => ({ readingLists: [...state.readingLists, list] })),
    310 
    311   addStoryToList: (listId, item) =>
     451  fetchReadingLists: async () => {
     452    try {
     453      const [listsRes, storiesRes] = await Promise.all([
     454        axios.get(`${API}/readinglists`),
     455        axios.get(`${API}/stories`),
     456      ])
     457      const data: any[] = listsRes.data?.readingLists ?? listsRes.data ?? []
     458      const storiesData: any[] = storiesRes.data?.stories ?? storiesRes.data ?? []
     459      const lists: ReadingList[] = data.map((l: any) => ({
     460        list_id: l.id,
     461        user_id: l.userId,
     462        username: '',
     463        name: l.name,
     464        description: l.content ?? '',
     465        is_public: l.isPublic,
     466        created_at: l.createdAt,
     467        stories: (l.readingListItems ?? []).map((i: any) => {
     468          const story = storiesData.find((s: any) => s.id === i.storyId)
     469          return {
     470            item_id: i.id ?? 0,
     471            list_id: l.id,
     472            story_id: i.storyId,
     473            story_title: story?.title ?? story?.shortDescription ?? `Story #${i.storyId}`,
     474            author_username: story?.authorUsername ?? '',
     475            added_at: i.addedAt ?? new Date().toISOString(),
     476            genres: story?.genres ?? [],
     477          }
     478        }),
     479      }))
     480      if (lists.length > 0) set({ readingLists: lists })
     481    } catch {
     482      // keep mock data on failure
     483    }
     484  },
     485
     486  createReadingList: async (list) => {
     487    set(state => ({ readingLists: [...state.readingLists, list] }))
     488    const res = await axios.post(`${API}/readinglists`, {
     489      name: list.name,
     490      content: list.description ?? null,
     491      isPublic: list.is_public,
     492      userId: list.user_id,
     493    }, { headers: getAuthHeaders() })
     494    const backendId = res.data?.id ?? res.data
     495    if (backendId && backendId !== list.list_id) {
     496      set(state => ({
     497        readingLists: state.readingLists.map(l =>
     498          l.list_id === list.list_id ? { ...l, list_id: backendId } : l
     499        ),
     500      }))
     501      return backendId
     502    }
     503    return list.list_id
     504  },
     505
     506  addStoryToList: async (listId, item) => {
    312507    set(state => ({
    313508      readingLists: state.readingLists.map(l =>
    314         l.list_id === listId
    315           ? { ...l, stories: [...l.stories, item] }
    316           : l
    317       ),
    318     })),
    319 
    320   removeStoryFromList: (listId, storyId) =>
     509        l.list_id === listId ? { ...l, stories: [...l.stories, item] } : l
     510      ),
     511    }))
     512    await axios.post(`${API}/readinglistitems`, {
     513      readingListId: listId,
     514      storyId: item.story_id,
     515    }, { headers: getAuthHeaders() })
     516  },
     517
     518  removeStoryFromList: async (listId, storyId) => {
    321519    set(state => ({
    322520      readingLists: state.readingLists.map(l =>
     
    325523          : l
    326524      ),
    327     })),
    328 
    329   deleteReadingList: (listId) =>
     525    }))
     526    try {
     527      // find the item id from backend list items to delete
     528      const res = await axios.get(`${API}/readinglistitems`)
     529      const items: any[] = res.data?.readingListItems ?? res.data ?? []
     530      const item = items.find((i: any) => i.readingListId === listId && i.storyId === storyId)
     531      if (item) await axios.delete(`${API}/readinglistitems/${item.id}`, { headers: getAuthHeaders() })
     532    } catch {
     533      // optimistic update already applied
     534    }
     535  },
     536
     537  deleteReadingList: async (listId) => {
    330538    set(state => ({
    331539      readingLists: state.readingLists.filter(l => l.list_id !== listId),
    332     })),
     540    }))
     541    await axios.delete(`${API}/readinglists/${listId}`, { headers: getAuthHeaders() })
     542  },
    333543}))
Note: See TracChangeset for help on using the changeset viewer.