Changeset 73b69b2 for chapterx-frontend/src/store
- Timestamp:
- 03/24/26 22:13:36 (3 months ago)
- Branches:
- main
- Children:
- 7fbb91c
- Parents:
- acf690c
- Location:
- chapterx-frontend/src/store
- Files:
-
- 2 edited
-
notificationStore.ts (modified) (1 diff)
-
storyStore.ts (modified) (9 diffs)
Legend:
- Unmodified
- Added
- Removed
-
chapterx-frontend/src/store/notificationStore.ts
racf690c r73b69b2 1 1 import { create } from 'zustand' 2 import axios from 'axios' 2 3 import { Notification } from '../types' 3 import { mockNotifications } from '../data/mockData' 4 5 const API = 'https://localhost:7125/api' 6 7 function getAuthHeaders() { 8 try { 9 const token = JSON.parse(localStorage.getItem('chapterx-auth') || '{}')?.state?.token 10 return token ? { Authorization: `Bearer ${token}` } : {} 11 } catch { 12 return {} 13 } 14 } 4 15 5 16 interface NotificationStore { 6 17 notifications: Notification[] 7 addNotification: (n: Omit<Notification, 'notification_id' | 'created_at' | 'is_read'>) => void 8 markAllRead: () => void 9 markRead: (id: number) => void 18 fetchUserNotifications: (userId: number) => Promise<void> 19 addNotification: (n: { recipientUserId: number; type: string; content: string; link?: string }) => Promise<void> 20 markAllRead: () => Promise<void> 21 markRead: (id: number) => Promise<void> 10 22 getUnreadCount: () => number 11 23 } 12 24 13 25 export const useNotificationStore = create<NotificationStore>((set, get) => ({ 14 notifications: [ ...mockNotifications],26 notifications: [], 15 27 16 addNotification: (n) => 17 set(state => ({ 18 notifications: [ 19 { 20 ...n, 21 notification_id: Date.now(), 22 created_at: new Date().toISOString(), 23 is_read: false, 24 }, 25 ...state.notifications, 26 ], 27 })), 28 fetchUserNotifications: async (userId) => { 29 try { 30 const res = await axios.get(`${API}/notifications/user/${userId}`, { headers: getAuthHeaders() }) 31 const data: any[] = res.data ?? [] 32 const notifications: Notification[] = data.map(n => ({ 33 notification_id: n.id, 34 user_id: userId, 35 type: n.type ?? 'info', 36 title: n.type ?? 'Notification', 37 message: n.content, 38 link: n.link, 39 is_read: n.isRead, 40 created_at: n.createdAt, 41 })) 42 set({ notifications }) 43 } catch { 44 // keep existing 45 } 46 }, 28 47 29 markAllRead: () => 30 set(state => ({ 31 notifications: state.notifications.map(n => ({ ...n, is_read: true })), 32 })), 48 addNotification: async ({ recipientUserId, type, content, link }) => { 49 try { 50 await axios.post(`${API}/notifications`, { 51 content, 52 recipientUserId, 53 type, 54 link, 55 }, { headers: getAuthHeaders() }) 56 } catch { 57 // silent — notification is for recipient, not current user 58 } 59 }, 33 60 34 markRead: (id) => 61 markAllRead: async () => { 62 const unread = get().notifications.filter(n => !n.is_read) 63 set(state => ({ notifications: state.notifications.map(n => ({ ...n, is_read: true })) })) 64 try { 65 await Promise.all(unread.map(n => 66 axios.put(`${API}/notifications/${n.notification_id}/read`, {}, { headers: getAuthHeaders() }) 67 )) 68 } catch { 69 // keep optimistic 70 } 71 }, 72 73 markRead: async (id) => { 35 74 set(state => ({ 36 75 notifications: state.notifications.map(n => 37 76 n.notification_id === id ? { ...n, is_read: true } : n 38 77 ), 39 })), 78 })) 79 try { 80 await axios.put(`${API}/notifications/${id}/read`, {}, { headers: getAuthHeaders() }) 81 } catch { 82 // keep optimistic 83 } 84 }, 40 85 41 86 getUnreadCount: () => get().notifications.filter(n => !n.is_read).length, -
chapterx-frontend/src/store/storyStore.ts
racf690c r73b69b2 25 25 const API = 'https://localhost:7125/api' 26 26 27 function mapReadingList(l: any): ReadingList { 28 return { 29 list_id: l.id, 30 user_id: l.userId, 31 username: l.username ?? '', 32 name: l.name, 33 description: l.content ?? '', 34 is_public: l.isPublic, 35 created_at: l.createdAt, 36 stories: (l.readingListItems ?? []).map((i: any) => ({ 37 item_id: i.listId ?? 0, 38 list_id: l.id, 39 story_id: i.storyId, 40 story_title: i.storyTitle ?? `Story #${i.storyId}`, 41 author_username: i.authorUsername ?? '', 42 added_at: i.addedAt ?? new Date().toISOString(), 43 genres: i.genres ?? [], 44 })), 45 } 46 } 47 27 48 function getAuthHeaders() { 28 49 try { … … 53 74 fetchChapters: () => Promise<void> 54 75 fetchReadingLists: () => Promise<void> 76 fetchUserReadingLists: (userId: number) => Promise<void> 77 fetchGenres: () => Promise<void> 55 78 56 79 // Story actions … … 86 109 87 110 // Genre actions 88 addGenre: ( genre: Genre) => void89 deleteGenre: (id: number) => void111 addGenre: (name: string) => Promise<void> 112 deleteGenre: (id: number) => Promise<void> 90 113 91 114 // Reading list actions … … 103 126 aiSuggestions: [...mockAISuggestions], 104 127 genres: [...mockGenres], 105 readingLists: [ ...mockReadingLists],128 readingLists: [], 106 129 likedStories: [], 107 130 … … 118 141 mature_content: s.matureContent, 119 142 status: 'published' as StoryStatus, 120 author_username: '',143 author_username: s.writer?.user?.username ?? '', 121 144 created_at: s.createdAt, 122 145 updated_at: s.updatedAt, … … 125 148 total_chapters: 0, 126 149 total_views: 0, 127 genres: [],150 genres: (s.hasGenres ?? []).map((hg: any) => hg.genre?.name ?? hg.name).filter(Boolean), 128 151 })) 129 152 if (stories.length > 0) set({ stories }) … … 159 182 const res = await axios.post(`${API}/stories`, { 160 183 matureContent: story.mature_content, 161 shortDescription: story. title || story.short_description,184 shortDescription: story.short_description || story.title, 162 185 image: null, 163 186 content: story.content, 164 187 userId: story.user_id, 188 genres: story.genres ?? [], 165 189 }, { headers: getAuthHeaders() }) 166 190 const backendId = res.data?.id ?? res.data … … 443 467 }, 444 468 445 addGenre: (genre) => 446 set(state => ({ genres: [...state.genres, genre] })), 447 448 deleteGenre: (id) => 449 set(state => ({ genres: state.genres.filter(g => g.genre_id !== id) })), 469 fetchGenres: async () => { 470 try { 471 const res = await axios.get(`${API}/genres`) 472 const data: any[] = res.data?.genres ?? res.data ?? [] 473 const genres: Genre[] = data.map((g: any) => ({ genre_id: g.id, name: g.name })) 474 if (genres.length > 0) set({ genres }) 475 } catch { 476 // keep mock data on failure 477 } 478 }, 479 480 addGenre: async (name) => { 481 const res = await axios.post(`${API}/genres`, { name }, { headers: getAuthHeaders() }) 482 const id = res.data?.id ?? res.data 483 set(state => ({ genres: [...state.genres, { genre_id: id, name }] })) 484 }, 485 486 deleteGenre: async (id) => { 487 set(state => ({ genres: state.genres.filter(g => g.genre_id !== id) })) 488 try { 489 await axios.delete(`${API}/genres/${id}`, { headers: getAuthHeaders() }) 490 } catch { 491 // optimistic delete already applied 492 } 493 }, 450 494 451 495 fetchReadingLists: async () => { 452 496 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 497 const res = await axios.get(`${API}/readinglists`) 498 const data: any[] = res.data ?? [] 499 const lists: ReadingList[] = data.map(mapReadingList) 500 set({ readingLists: lists }) 501 } catch { 502 // keep existing data on failure 503 } 504 }, 505 506 fetchUserReadingLists: async (userId) => { 507 try { 508 const res = await axios.get(`${API}/readinglists/user/${userId}`, { headers: getAuthHeaders() }) 509 const data: any[] = res.data ?? [] 510 const lists: ReadingList[] = data.map(mapReadingList) 511 set(state => ({ 512 readingLists: [ 513 ...state.readingLists.filter(l => l.user_id !== userId), 514 ...lists, 515 ] 516 })) 517 } catch (err) { 518 console.error('fetchUserReadingLists failed:', err) 483 519 } 484 520 }, … … 525 561 })) 526 562 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() }) 563 await axios.delete(`${API}/readinglistitems/${listId}/story/${storyId}`, { headers: getAuthHeaders() }) 532 564 } catch { 533 565 // optimistic update already applied
Note:
See TracChangeset
for help on using the changeset viewer.
