source: chapterx-frontend/src/store/storyStore.ts@ b62cefc

main
Last change on this file since b62cefc was b62cefc, checked in by kikisrbinoska <srbinoskakristina07@…>, 4 months ago

Added frontend and imporoved AI Suggestions service

  • Property mode set to 100644
File size: 9.6 KB
Line 
1import { create } from 'zustand'
2import axios from 'axios'
3import {
4 Story,
5 Chapter,
6 Comment,
7 Collaboration,
8 AISuggestion,
9 Genre,
10 ReadingList,
11 ReadingListItem,
12 StoryStatus,
13 PermissionLevel,
14} from '../types'
15import {
16 mockStories,
17 mockChapters,
18 mockComments,
19 mockCollaborations,
20 mockAISuggestions,
21 mockGenres,
22 mockReadingLists,
23} from '../data/mockData'
24
25interface LikeRecord {
26 userId: number
27 storyId: number
28}
29
30interface StoryStore {
31 stories: Story[]
32 chapters: Chapter[]
33 comments: Comment[]
34 collaborations: Collaboration[]
35 aiSuggestions: AISuggestion[]
36 genres: Genre[]
37 readingLists: ReadingList[]
38 likedStories: LikeRecord[]
39
40 // Story actions
41 addStory: (story: Story) => void
42 updateStory: (id: number, partial: Partial<Story>) => void
43 deleteStory: (id: number) => void
44 updateStoryStatus: (id: number, status: StoryStatus) => void
45
46 // Chapter actions
47 addChapter: (chapter: Chapter) => void
48 updateChapter: (id: number, partial: Partial<Chapter>) => void
49 deleteChapter: (id: number) => void
50 incrementViewCount: (chapterId: number) => void
51
52 // Comment actions
53 addComment: (comment: Comment) => void
54 deleteComment: (id: number) => void
55
56 // Like actions
57 toggleLike: (userId: number, storyId: number) => void
58 isLiked: (userId: number, storyId: number) => boolean
59
60 // Collaboration actions
61 addCollaboration: (collab: Collaboration) => void
62 updateCollaborationPermission: (userId: number, storyId: number, level: PermissionLevel) => void
63 removeCollaboration: (userId: number, storyId: number) => void
64
65 // AI Suggestion actions
66 fetchSuggestions: () => Promise<void>
67 acceptSuggestion: (id: number) => Promise<void>
68 rejectSuggestion: (id: number) => Promise<void>
69 addSuggestion: (suggestion: Omit<AISuggestion, 'suggestion_id'>) => Promise<void>
70
71 // Genre actions
72 addGenre: (genre: Genre) => void
73 deleteGenre: (id: number) => void
74
75 // 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
80}
81
82export const useStoryStore = create<StoryStore>((set, get) => ({
83 stories: [...mockStories],
84 chapters: [...mockChapters],
85 comments: [...mockComments],
86 collaborations: [...mockCollaborations],
87 aiSuggestions: [...mockAISuggestions],
88 genres: [...mockGenres],
89 readingLists: [...mockReadingLists],
90 likedStories: [],
91
92 addStory: (story) =>
93 set(state => ({ stories: [...state.stories, story] })),
94
95 updateStory: (id, partial) =>
96 set(state => ({
97 stories: state.stories.map(s => (s.story_id === id ? { ...s, ...partial } : s)),
98 })),
99
100 deleteStory: (id) =>
101 set(state => ({
102 stories: state.stories.filter(s => s.story_id !== id),
103 chapters: state.chapters.filter(c => c.story_id !== id),
104 comments: state.comments.filter(c => c.story_id !== id),
105 collaborations: state.collaborations.filter(c => c.story_id !== id),
106 })),
107
108 updateStoryStatus: (id, status) =>
109 set(state => ({
110 stories: state.stories.map(s =>
111 s.story_id === id ? { ...s, status, updated_at: new Date().toISOString() } : s
112 ),
113 })),
114
115 addChapter: (chapter) =>
116 set(state => ({
117 chapters: [...state.chapters, chapter],
118 stories: state.stories.map(s =>
119 s.story_id === chapter.story_id
120 ? { ...s, total_chapters: s.total_chapters + 1 }
121 : s
122 ),
123 })),
124
125 updateChapter: (id, partial) =>
126 set(state => ({
127 chapters: state.chapters.map(c =>
128 c.chapter_id === id ? { ...c, ...partial, updated_at: new Date().toISOString() } : c
129 ),
130 })),
131
132 deleteChapter: (id) =>
133 set(state => {
134 const chapter = state.chapters.find(c => c.chapter_id === id)
135 return {
136 chapters: state.chapters.filter(c => c.chapter_id !== id),
137 stories: chapter
138 ? state.stories.map(s =>
139 s.story_id === chapter.story_id
140 ? { ...s, total_chapters: Math.max(0, s.total_chapters - 1) }
141 : s
142 )
143 : state.stories,
144 }
145 }),
146
147 incrementViewCount: (chapterId) =>
148 set(state => ({
149 chapters: state.chapters.map(c =>
150 c.chapter_id === chapterId ? { ...c, view_count: c.view_count + 1 } : c
151 ),
152 })),
153
154 addComment: (comment) =>
155 set(state => ({
156 comments: [...state.comments, comment],
157 stories: state.stories.map(s =>
158 s.story_id === comment.story_id
159 ? { ...s, total_comments: s.total_comments + 1 }
160 : s
161 ),
162 })),
163
164 deleteComment: (id) =>
165 set(state => {
166 const comment = state.comments.find(c => c.comment_id === id)
167 return {
168 comments: state.comments.filter(c => c.comment_id !== id),
169 stories: comment
170 ? state.stories.map(s =>
171 s.story_id === comment.story_id
172 ? { ...s, total_comments: Math.max(0, s.total_comments - 1) }
173 : s
174 )
175 : state.stories,
176 }
177 }),
178
179 toggleLike: (userId, storyId) =>
180 set(state => {
181 const exists = state.likedStories.some(
182 l => l.userId === userId && l.storyId === storyId
183 )
184 return {
185 likedStories: exists
186 ? state.likedStories.filter(l => !(l.userId === userId && l.storyId === storyId))
187 : [...state.likedStories, { userId, storyId }],
188 stories: state.stories.map(s =>
189 s.story_id === storyId
190 ? { ...s, total_likes: exists ? s.total_likes - 1 : s.total_likes + 1 }
191 : s
192 ),
193 }
194 }),
195
196 isLiked: (userId, storyId) =>
197 get().likedStories.some(l => l.userId === userId && l.storyId === storyId),
198
199 addCollaboration: (collab) =>
200 set(state => ({ collaborations: [...state.collaborations, collab] })),
201
202 updateCollaborationPermission: (userId, storyId, level) =>
203 set(state => ({
204 collaborations: state.collaborations.map(c =>
205 c.user_id === userId && c.story_id === storyId
206 ? { ...c, permission_level: level }
207 : c
208 ),
209 })),
210
211 removeCollaboration: (userId, storyId) =>
212 set(state => ({
213 collaborations: state.collaborations.filter(
214 c => !(c.user_id === userId && c.story_id === storyId)
215 ),
216 })),
217
218 fetchSuggestions: async () => {
219 try {
220 const res = await axios.get('https://localhost:7125/api/aisuggestions')
221 const data = res.data.aiSuggestions ?? res.data
222 const mapped: AISuggestion[] = data.map((s: any) => ({
223 suggestion_id: s.id,
224 chapter_id: s.storyId,
225 story_id: s.storyId,
226 original_text: s.originalText,
227 suggested_text: s.suggestedText,
228 suggestion_type: 'style' as const,
229 accepted: s.accepted === true ? true : s.accepted === false ? false : null,
230 applied_at: s.appliedAt ?? undefined,
231 }))
232 set({ aiSuggestions: mapped })
233 } catch {
234 // keep mock data on failure
235 }
236 },
237
238 acceptSuggestion: async (id) => {
239 const s = get().aiSuggestions.find(s => s.suggestion_id === id)
240 if (!s) return
241 // optimistic update
242 set(state => ({
243 aiSuggestions: state.aiSuggestions.map(s =>
244 s.suggestion_id === id
245 ? { ...s, accepted: true, applied_at: new Date().toISOString() }
246 : s
247 ),
248 }))
249 try {
250 await axios.put(`https://localhost:7125/api/aisuggestions/${id}`, {
251 id,
252 originalText: s.original_text,
253 suggestedText: s.suggested_text,
254 accepted: true,
255 })
256 } catch {
257 // keep optimistic update even if backend fails
258 }
259 },
260
261 rejectSuggestion: async (id) => {
262 const s = get().aiSuggestions.find(s => s.suggestion_id === id)
263 if (!s) return
264 set(state => ({
265 aiSuggestions: state.aiSuggestions.map(s =>
266 s.suggestion_id === id ? { ...s, accepted: false } : s
267 ),
268 }))
269 try {
270 await axios.put(`https://localhost:7125/api/aisuggestions/${id}`, {
271 id,
272 originalText: s.original_text,
273 suggestedText: s.suggested_text,
274 accepted: false,
275 })
276 } catch {
277 // keep optimistic update even if backend fails
278 }
279 },
280
281 addSuggestion: async (suggestion) => {
282 // optimistic local add
283 const tempId = Date.now()
284 set(state => ({ aiSuggestions: [...state.aiSuggestions, { ...suggestion, suggestion_id: tempId }] }))
285 try {
286 const res = await axios.post('https://localhost:7125/api/aisuggestions', {
287 originalText: suggestion.original_text,
288 suggestedText: suggestion.suggested_text,
289 storyId: suggestion.chapter_id,
290 })
291 const newId = res.data.id ?? tempId
292 set(state => ({
293 aiSuggestions: state.aiSuggestions.map(s =>
294 s.suggestion_id === tempId ? { ...s, suggestion_id: newId } : s
295 ),
296 }))
297 } catch {
298 // keep local suggestion on failure
299 }
300 },
301
302 addGenre: (genre) =>
303 set(state => ({ genres: [...state.genres, genre] })),
304
305 deleteGenre: (id) =>
306 set(state => ({ genres: state.genres.filter(g => g.genre_id !== id) })),
307
308 createReadingList: (list) =>
309 set(state => ({ readingLists: [...state.readingLists, list] })),
310
311 addStoryToList: (listId, item) =>
312 set(state => ({
313 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) =>
321 set(state => ({
322 readingLists: state.readingLists.map(l =>
323 l.list_id === listId
324 ? { ...l, stories: l.stories.filter(s => s.story_id !== storyId) }
325 : l
326 ),
327 })),
328
329 deleteReadingList: (listId) =>
330 set(state => ({
331 readingLists: state.readingLists.filter(l => l.list_id !== listId),
332 })),
333}))
Note: See TracBrowser for help on using the repository browser.