Skip to content

Commit 74b9896

Browse files
authored
Merge pull request #30 from dusvlf111/issue/#27
feat: implement message editing and deletion functionality in chat co…
2 parents 2d3e99e + 8786cd7 commit 74b9896

6 files changed

Lines changed: 301 additions & 66 deletions

File tree

src/contexts/ChatContext.tsx

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ interface ChatState {
1313
}
1414

1515
interface ChatAction {
16-
type: 'SET_MESSAGES' | 'ADD_MESSAGE' | 'SET_LOADING' | 'SET_SENDING' | 'SET_ERROR' | 'SET_ISSUE' | 'SET_ISSUE_NUMBER' | 'SET_REFRESHING';
16+
type: 'SET_MESSAGES' | 'ADD_MESSAGE' | 'UPDATE_MESSAGE' | 'DELETE_MESSAGE' | 'SET_LOADING' | 'SET_SENDING' | 'SET_ERROR' | 'SET_ISSUE' | 'SET_ISSUE_NUMBER' | 'SET_REFRESHING';
1717
payload: any;
1818
}
1919

@@ -33,6 +33,18 @@ const chatReducer = (state: ChatState, action: ChatAction): ChatState => {
3333
return { ...state, messages: action.payload, loading: false };
3434
case 'ADD_MESSAGE':
3535
return { ...state, messages: [...state.messages, action.payload] };
36+
case 'UPDATE_MESSAGE':
37+
return {
38+
...state,
39+
messages: state.messages.map(msg =>
40+
msg.id === action.payload.id ? { ...msg, content: action.payload.content, isEdited: true } : msg
41+
)
42+
};
43+
case 'DELETE_MESSAGE':
44+
return {
45+
...state,
46+
messages: state.messages.filter(msg => msg.id !== action.payload)
47+
};
3648
case 'SET_LOADING':
3749
return { ...state, loading: action.payload };
3850
case 'SET_SENDING':
@@ -54,6 +66,8 @@ interface ChatContextType {
5466
state: ChatState;
5567
dispatch: React.Dispatch<ChatAction>;
5668
sendMessage: (content: string, issueNumber?: number) => Promise<void>;
69+
editMessage: (messageId: number, content: string) => Promise<void>;
70+
deleteMessage: (messageId: number) => Promise<void>;
5771
refreshMessages: (issueNumber?: number) => Promise<void>;
5872
fetchIssueDetails: (issueNumber?: number) => Promise<void>;
5973
setCurrentIssueNumber: (issueNumber: number) => void;
@@ -169,6 +183,105 @@ export const ChatProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
169183
}
170184
}, [state.currentIssueNumber, state.isRefreshing]);
171185

186+
// 즉시 새로고침 함수 (디바운싱 없음)
187+
const refreshMessagesImmediately = useCallback(async (issueNumber?: number) => {
188+
try {
189+
console.log('🚀 Immediate refresh called with issueNumber:', issueNumber);
190+
191+
const token = localStorage.getItem('github_token');
192+
if (!token) {
193+
console.log('❌ No token found, returning early');
194+
return;
195+
}
196+
197+
const targetIssueNumber = issueNumber || state.currentIssueNumber;
198+
if (!targetIssueNumber) {
199+
console.log('❌ No issue number found, returning early');
200+
return;
201+
}
202+
203+
console.log('📡 Fetching comments from GitHub API immediately...');
204+
dispatch({ type: 'SET_LOADING', payload: true });
205+
206+
const comments = await githubAPI.getMessages(token, {}, targetIssueNumber);
207+
console.log('📨 Comments received:', comments.length);
208+
209+
const messages: ChatMessage[] = comments.map(comment => ({
210+
id: comment.id,
211+
content: comment.body,
212+
author: {
213+
id: comment.user.id,
214+
username: comment.user.login,
215+
avatar: comment.user.avatar_url,
216+
},
217+
timestamp: new Date(comment.created_at).toLocaleString('ko-KR'),
218+
isEdited: comment.created_at !== comment.updated_at,
219+
isOwn: false,
220+
htmlUrl: comment.html_url,
221+
}));
222+
223+
console.log('💬 Messages converted:', messages.length);
224+
dispatch({ type: 'SET_MESSAGES', payload: messages });
225+
dispatch({ type: 'SET_LOADING', payload: false });
226+
console.log('✅ Immediate refresh completed successfully');
227+
228+
} catch (error) {
229+
console.error('❌ 즉시 새로고침 실패:', error);
230+
dispatch({ type: 'SET_ERROR', payload: '메시지를 불러오는데 실패했습니다.' });
231+
dispatch({ type: 'SET_LOADING', payload: false });
232+
}
233+
}, [state.currentIssueNumber]);
234+
235+
const editMessage = useCallback(async (messageId: number, content: string) => {
236+
try {
237+
const token = localStorage.getItem('github_token');
238+
if (!token) throw new Error('토큰이 없습니다.');
239+
240+
await githubAPI.editMessage(token, messageId, content);
241+
242+
// 메시지 수정 후 즉시 새로고침
243+
const targetIssueNumber = state.currentIssueNumber;
244+
if (targetIssueNumber) {
245+
console.log('🔄 Auto-refreshing messages after edit...');
246+
await refreshMessagesImmediately(targetIssueNumber);
247+
} else {
248+
// 새로고침이 실패하면 로컬 상태에서만 업데이트
249+
dispatch({
250+
type: 'UPDATE_MESSAGE',
251+
payload: {
252+
id: messageId,
253+
content: content
254+
}
255+
});
256+
}
257+
} catch (error) {
258+
console.error('메시지 수정 실패:', error);
259+
dispatch({ type: 'SET_ERROR', payload: '메시지 수정에 실패했습니다.' });
260+
}
261+
}, [state.currentIssueNumber, refreshMessagesImmediately]);
262+
263+
const deleteMessage = useCallback(async (messageId: number) => {
264+
try {
265+
const token = localStorage.getItem('github_token');
266+
if (!token) throw new Error('토큰이 없습니다.');
267+
268+
await githubAPI.deleteMessage(token, messageId);
269+
270+
// 메시지 삭제 후 즉시 새로고침
271+
const targetIssueNumber = state.currentIssueNumber;
272+
if (targetIssueNumber) {
273+
console.log('🔄 Auto-refreshing messages after deletion...');
274+
await refreshMessagesImmediately(targetIssueNumber);
275+
} else {
276+
// 새로고침이 실패하면 로컬 상태에서만 제거
277+
dispatch({ type: 'DELETE_MESSAGE', payload: messageId });
278+
}
279+
} catch (error) {
280+
console.error('메시지 삭제 실패:', error);
281+
dispatch({ type: 'SET_ERROR', payload: '메시지 삭제에 실패했습니다.' });
282+
}
283+
}, [state.currentIssueNumber, refreshMessagesImmediately]);
284+
172285
const fetchIssueDetails = useCallback(async (issueNumber?: number) => {
173286
try {
174287
const token = localStorage.getItem('github_token');
@@ -194,6 +307,8 @@ export const ChatProvider: React.FC<{ children: ReactNode }> = ({ children }) =>
194307
state,
195308
dispatch,
196309
sendMessage,
310+
editMessage,
311+
deleteMessage,
197312
refreshMessages,
198313
fetchIssueDetails,
199314
setCurrentIssueNumber

src/pages/ChatPage/ChatPage.scss

Lines changed: 66 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -236,36 +236,29 @@
236236

237237
.message {
238238
display: flex;
239-
gap: 12px;
240-
align-items: flex-start;
239+
margin-bottom: 1rem;
240+
gap: 0.75rem;
241241

242242
&--own {
243243
flex-direction: row-reverse;
244-
244+
245245
.message-content {
246-
background: var(--primary-color);
246+
background-color: var(--primary-color);
247247
color: white;
248-
border-radius: 18px 18px 4px 18px;
249-
250-
.message-header {
251-
.message-author {
252-
color: rgba(255, 255, 255, 0.9);
253-
}
254-
255-
.message-time {
256-
color: rgba(255, 255, 255, 0.7);
257-
}
258-
}
248+
border-radius: 1rem 1rem 0.25rem 1rem;
249+
}
250+
251+
.message-actions {
252+
justify-content: flex-end;
259253
}
260254
}
261255

262-
.message-avatar {
263-
flex-shrink: 0;
264-
width: 40px;
265-
height: 40px;
256+
&-avatar {
257+
width: 2.5rem;
258+
height: 2.5rem;
266259
border-radius: 50%;
267260
overflow: hidden;
268-
border: 2px solid var(--border-color);
261+
flex-shrink: 0;
269262

270263
img {
271264
width: 100%;
@@ -274,35 +267,46 @@
274267
}
275268
}
276269

277-
.message-content {
278-
flex: 1;
279-
background: var(--bg-color);
280-
border: 1px solid var(--border-color);
281-
border-radius: 18px 18px 18px 4px;
282-
padding: 12px 16px;
270+
&-content {
271+
background-color: var(--background-secondary);
272+
padding: 0.75rem 1rem;
273+
border-radius: 1rem 1rem 1rem 0.25rem;
283274
max-width: 70%;
275+
position: relative;
276+
}
284277

285-
.message-header {
286-
display: flex;
287-
justify-content: space-between;
288-
align-items: center;
289-
margin-bottom: 4px;
290-
font-size: 0.8rem;
278+
&-header {
279+
display: flex;
280+
align-items: center;
281+
gap: 0.5rem;
282+
margin-bottom: 0.25rem;
283+
font-size: 0.875rem;
284+
}
291285

292-
.message-author {
293-
font-weight: 600;
294-
color: var(--text-color);
295-
}
286+
&-author {
287+
font-weight: 600;
288+
color: var(--text-primary);
289+
}
296290

297-
.message-time {
298-
color: var(--text-muted);
299-
}
300-
}
291+
&-time {
292+
color: var(--text-secondary);
293+
font-size: 0.75rem;
294+
}
301295

302-
.message-text {
303-
line-height: 1.4;
304-
word-wrap: break-word;
305-
}
296+
&-edited {
297+
color: var(--text-tertiary);
298+
font-style: italic;
299+
}
300+
301+
&-text {
302+
line-height: 1.4;
303+
word-wrap: break-word;
304+
}
305+
306+
&-actions {
307+
display: flex;
308+
gap: 0.25rem;
309+
margin-top: 0.5rem;
306310
}
307311
}
308312
}
@@ -351,6 +355,24 @@
351355
}
352356
}
353357

358+
.message-action-btn {
359+
background: none;
360+
border: none;
361+
padding: 0.25rem;
362+
border-radius: 0.25rem;
363+
cursor: pointer;
364+
font-size: 0.875rem;
365+
}
366+
367+
.edit-message-modal {
368+
.edit-message-actions {
369+
display: flex;
370+
gap: 0.75rem;
371+
justify-content: flex-end;
372+
margin-top: 1rem;
373+
}
374+
}
375+
354376
// 다크 모드 대응
355377
[data-theme="dark"] {
356378
.chat-page {

0 commit comments

Comments
 (0)