Changeset acf690c for chapterx-frontend/src/store
- Timestamp:
- 03/22/26 17:58:40 (4 months ago)
- Branches:
- main
- Children:
- 73b69b2
- Parents:
- b62cefc
- Location:
- chapterx-frontend/src/store
- Files:
-
- 2 edited
-
authStore.ts (modified) (3 diffs)
-
storyStore.ts (modified) (14 diffs)
Legend:
- Unmodified
- Added
- Removed
-
chapterx-frontend/src/store/authStore.ts
rb62cefc racf690c 27 27 token: null, 28 28 showMatureContent: true, 29 allUsers: [ ...mockUsers],29 allUsers: [], 30 30 31 31 login: async (emailOrUsername, password) => { … … 36 36 : get().allUsers.find(u => u.username === emailOrUsername)?.email || emailOrUsername 37 37 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 } 51 50 set({ currentUser: user, token }) 52 51 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 55 60 } 56 61 … … 66 71 67 72 register: async (data, role) => { 68 // Try backend first69 73 try { 70 74 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) 73 98 } 74 99 -
chapterx-frontend/src/store/storyStore.ts
rb62cefc racf690c 23 23 } from '../data/mockData' 24 24 25 const API = 'https://localhost:7125/api' 26 27 function 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 25 36 interface LikeRecord { 26 37 userId: number … … 38 49 likedStories: LikeRecord[] 39 50 51 // Fetch from backend 52 fetchStories: () => Promise<void> 53 fetchChapters: () => Promise<void> 54 fetchReadingLists: () => Promise<void> 55 40 56 // Story actions 41 addStory: (story: Story) => void42 updateStory: (id: number, partial: Partial<Story>) => void43 deleteStory: (id: number) => void57 addStory: (story: Story) => Promise<number> 58 updateStory: (id: number, partial: Partial<Story>) => Promise<void> 59 deleteStory: (id: number) => Promise<void> 44 60 updateStoryStatus: (id: number, status: StoryStatus) => void 45 61 46 62 // Chapter actions 47 addChapter: (chapter: Chapter) => void48 updateChapter: (id: number, partial: Partial<Chapter>) => void49 deleteChapter: (id: number) => void63 addChapter: (chapter: Chapter) => Promise<void> 64 updateChapter: (id: number, partial: Partial<Chapter>) => Promise<void> 65 deleteChapter: (id: number) => Promise<void> 50 66 incrementViewCount: (chapterId: number) => void 51 67 … … 74 90 75 91 // Reading list actions 76 createReadingList: (list: ReadingList) => void77 addStoryToList: (listId: number, item: ReadingListItem) => void78 removeStoryFromList: (listId: number, storyId: number) => void79 deleteReadingList: (listId: number) => void92 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> 80 96 } 81 97 … … 90 106 likedStories: [], 91 107 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) => { 96 182 set(state => ({ 97 183 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) => { 101 201 set(state => ({ 102 202 stories: state.stories.filter(s => s.story_id !== id), … … 104 204 comments: state.comments.filter(c => c.story_id !== id), 105 205 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 }, 107 213 108 214 updateStoryStatus: (id, status) => … … 113 219 })), 114 220 115 addChapter: (chapter) =>221 addChapter: async (chapter) => { 116 222 set(state => ({ 117 223 chapters: [...state.chapters, chapter], … … 121 227 : s 122 228 ), 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) => { 126 248 set(state => ({ 127 249 chapters: state.chapters.map(c => 128 250 c.chapter_id === id ? { ...c, ...partial, updated_at: new Date().toISOString() } : c 129 251 ), 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) => { 133 270 set(state => { 134 271 const chapter = state.chapters.find(c => c.chapter_id === id) … … 143 280 : state.stories, 144 281 } 145 }), 282 }) 283 try { 284 await axios.delete(`${API}/chapters/${id}`, { headers: getAuthHeaders() }) 285 } catch { 286 // keep optimistic delete on failure 287 } 288 }, 146 289 147 290 incrementViewCount: (chapterId) => … … 218 361 fetchSuggestions: async () => { 219 362 try { 220 const res = await axios.get( 'https://localhost:7125/api/aisuggestions')363 const res = await axios.get(`${API}/aisuggestions`) 221 364 const data = res.data.aiSuggestions ?? res.data 222 365 const mapped: AISuggestion[] = data.map((s: any) => ({ … … 248 391 })) 249 392 try { 250 await axios.put(` https://localhost:7125/api/aisuggestions/${id}`, {393 await axios.put(`${API}/aisuggestions/${id}`, { 251 394 id, 252 395 originalText: s.original_text, … … 268 411 })) 269 412 try { 270 await axios.put(` https://localhost:7125/api/aisuggestions/${id}`, {413 await axios.put(`${API}/aisuggestions/${id}`, { 271 414 id, 272 415 originalText: s.original_text, … … 284 427 set(state => ({ aiSuggestions: [...state.aiSuggestions, { ...suggestion, suggestion_id: tempId }] })) 285 428 try { 286 const res = await axios.post(' https://localhost:7125/api/aisuggestions', {429 const res = await axios.post('${API}/aisuggestions', { 287 430 originalText: suggestion.original_text, 288 431 suggestedText: suggestion.suggested_text, … … 306 449 set(state => ({ genres: state.genres.filter(g => g.genre_id !== id) })), 307 450 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) => { 312 507 set(state => ({ 313 508 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) => { 321 519 set(state => ({ 322 520 readingLists: state.readingLists.map(l => … … 325 523 : l 326 524 ), 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) => { 330 538 set(state => ({ 331 539 readingLists: state.readingLists.filter(l => l.list_id !== listId), 332 })), 540 })) 541 await axios.delete(`${API}/readinglists/${listId}`, { headers: getAuthHeaders() }) 542 }, 333 543 }))
Note:
See TracChangeset
for help on using the changeset viewer.
