Skip to content

Commit 738e4ac

Browse files
committed
add saved posts list page
1 parent 58a6d88 commit 738e4ac

File tree

13 files changed

+232
-37
lines changed

13 files changed

+232
-37
lines changed

src/app/core/models/post.models.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface PostCardInfo {
1313
downvote: number;
1414
public: boolean;
1515
allowComment: boolean;
16+
isSaved?: boolean;
1617
}
1718

1819
export interface Tag {
@@ -164,3 +165,13 @@ export interface PostDataCreateRequest {
164165
isLectureVideo: boolean;
165166
isTextbook: boolean;
166167
}
168+
169+
export type SavedPostResponse = {
170+
id: string;
171+
saveAt: string;
172+
post: {
173+
id: string;
174+
postId: string;
175+
title: string;
176+
};
177+
};

src/app/core/router-manager/vetical-menu-dynamic/post-vertical-menu.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { SidebarItem } from '../../models/data-handle';
33
export function sidebarPosts(role: string): SidebarItem[] {
44
return [
55
{
6-
id: 'populat',
7-
path: 'exercise/popular',
8-
label: 'Bài viết phổ biến',
6+
id: 'saved-posts',
7+
path: '/post-features/saved-posts-list',
8+
label: 'Bài viết đã lưu',
99
icon: 'fas fa-file-alt',
1010
},
1111
{
1212
id: 'exercise',
13-
path: '/post-management/post-list',
13+
path: '/post-features/post-list',
1414
label: 'Quản lý bài viết',
1515
icon: 'fas fa-tasks',
1616
},

src/app/core/services/api-service/post.service.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
CreatePostRequest,
1212
postData,
1313
PostResponse,
14+
SavedPostResponse,
1415
} from '../../models/post.models';
1516

1617
@Injectable({
@@ -142,4 +143,23 @@ export class PostService {
142143
API_CONFIG.ENDPOINTS.DELETE.DELETE_POST(id)
143144
);
144145
}
146+
147+
savePost(postId: string) {
148+
return this.api.post<ApiResponse<null>>(
149+
API_CONFIG.ENDPOINTS.POST.SAVE_POST(postId),
150+
null
151+
);
152+
}
153+
154+
unSavePost(postId: string) {
155+
return this.api.delete<ApiResponse<null>>(
156+
API_CONFIG.ENDPOINTS.POST.SAVE_POST(postId)
157+
);
158+
}
159+
160+
getSavedPosts(page: number, size: number) {
161+
return this.api.get<ApiResponse<IPaginationResponse<SavedPostResponse[]>>>(
162+
API_CONFIG.ENDPOINTS.GET.GET_SAVED_POSTS(page, size)
163+
);
164+
}
145165
}

src/app/core/services/config-service/api.enpoints.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ export const API_CONFIG = {
129129
GET_CHAT_MESSAGES: (page: number, size: number, conversationId: string) =>
130130
`/chat/messages?page=${page}&size=${size}&conversationId=${conversationId}`,
131131
GET_POST_DETAILS: (postId: string) => `/post/${postId}`,
132+
GET_SAVED_POSTS: (page: number, size: number) =>
133+
`/profile/posts/saved?page=${page}&size=${size}`,
132134
GET_COMMENT_BY_POST_ID: (
133135
postId: string,
134136
page: number,
@@ -202,6 +204,7 @@ export const API_CONFIG = {
202204
SET_ROLE_FOR_USER_CHAT: (groupId: string) =>
203205
`/chat/conversation/group/${groupId}/role`,
204206
REACTION_POST: (postId: string) => `/post/${postId}/reaction/toggle`,
207+
SAVE_POST: (postId: string) => `/profile/post/${postId}/save`,
205208
ADD_COMMENT_POST: (postId: string) => `/post/${postId}/comment`,
206209
ADD_REPLY_COMMENT_POST: (postId: string, commentId: string) =>
207210
`/post/${postId}/comment/${commentId}`,
@@ -242,6 +245,7 @@ export const API_CONFIG = {
242245
DELETE_GROUP_CHAT: (groupId: string) =>
243246
`/chat/conversation/group/${groupId}`,
244247
DELETE_POST: (postId: string) => `/post/${postId}`,
248+
UNSAVE_POST: (postId: string) => `/profile/post/${postId}/save`,
245249
DELETE_COMMENT_POST: (commentId: string) => `/post/comment/${commentId}`,
246250
UNSAVE_EXERCISE: (exerciseId: string) =>
247251
`/profile/exercise/${exerciseId}/save`,

src/app/features/auth/pages/login/login.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,10 @@ <h2>CodeCampus</h2>
113113
<img src="/auth-assets/Google.png" alt="" class="google-icon" />
114114
Đăng nhập bằng Google
115115
</button>
116-
<button class="social-btn facebook" (click)="onFacebookLogin()">
116+
<!-- <button class="social-btn facebook" (click)="onFacebookLogin()">
117117
<img src="/auth-assets/Facebook.png" alt="" class="google-icon" />Đăng
118118
nhập bằng Facebook
119-
</button>
119+
</button> -->
120120

121121
<div class="signup-link">
122122
Bạn chưa có tài khoản?

src/app/features/conversation-chat/pages/chat/chat.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ <h2>Chats</h2>
7575
<img
7676
class="avatar"
7777
[src]="convo.conversationAvatar || avatarUrlDefault"
78-
alt="User Avatar"
78+
alt=""
7979
/>
8080

8181
<div *ngIf="convo.unreadCount > 0" class="unread-wrapper">

src/app/features/post/pages/post-list/post-list.html

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
(upvote)="handleUpVote(post.id)"
7676
(downvote)="handleDownVote(post.id)"
7777
(mainClick)="goToDetail($event)"
78+
(save)="handleToggleSave(post.id)"
7879
></app-post-card>
7980
</div>
8081
</div>
@@ -96,40 +97,11 @@
9697
</ng-lottie>
9798
<p>Không có dữ liệu</p>
9899
</div>
99-
100-
<!-- Phân trang (bỏ chú thích nếu cần) -->
101-
<!-- <div class="list-post-pagination">
102-
<app-pagination
103-
[totalData]="posts.length"
104-
[amountDataPerPage]="10"
105-
[currentPageIndex]="1"
106-
(onPageChange)="handlePageChange($event)"
107-
></app-pagination>
108-
</div> -->
109100
</div>
110101
</div>
111102

112103
<div class="popular">
113-
<!-- <div class="note-card">
114-
<div class="note-card-item">
115-
<div class="line" style="background-color: #09d3ae"></div>
116-
<span>Đã duyệt</span>
117-
</div>
118-
119-
<div class="note-card-item">
120-
<div class="line" style="background-color: #f5d905"></div>
121-
<span>Chờ duyệt</span>
122-
</div>
123-
<div class="note-card-item">
124-
<div class="line" style="background-color: #ff7a7c"></div>
125-
<span>Đã từ chối</span>
126-
</div>
127-
</div> -->
128104
<div class="popular-content">
129-
<!-- <app-popular-content
130-
[numTags]="fakeTags.length"
131-
[tags]="fakeTags"
132-
></app-popular-content> -->
133105
<app-trending
134106
[items]="trendingData"
135107
title="Popular Technologies"

src/app/features/post/pages/post-list/post-list.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,35 @@ export class PostListComponent {
241241
});
242242
}
243243

244+
handleToggleSave(postId: string) {
245+
const post = this.posts.find((p) => p.id === postId);
246+
if (!post) return;
247+
248+
// Nếu đang lưu thì gọi unSave
249+
if (post.isSaved) {
250+
this.postservice.unSavePost(postId).subscribe({
251+
next: () => {
252+
post.isSaved = false; // ✅ cập nhật lại trạng thái
253+
console.log(`Unsave thành công post: ${postId}`);
254+
},
255+
error: (err) => {
256+
console.error('Unsave thất bại', err);
257+
},
258+
});
259+
} else {
260+
// Nếu chưa lưu thì gọi save
261+
this.postservice.savePost(postId).subscribe({
262+
next: () => {
263+
post.isSaved = true;
264+
console.log(`Save thành công post: ${postId}`);
265+
},
266+
error: (err) => {
267+
console.error('Save thất bại', err);
268+
},
269+
});
270+
}
271+
}
272+
244273
handleInputChange(value: string | number): void {
245274
this.postname = value.toString();
246275

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<div class="saved-posts-container" appScrollEnd (appScrollEnd)="onScrollEnd()">
2+
<div
3+
class="saved-post-card"
4+
*ngFor="let item of savedPosts"
5+
(click)="navigateToDetails(item.post.postId)"
6+
>
7+
<div class="card-content">
8+
<h3 class="post-title">{{ item.post.title }}</h3>
9+
<p class="save-date">Đã lưu: {{ item.saveAt | date : "short" }}</p>
10+
</div>
11+
</div>
12+
13+
<div class="loading-spinner" *ngIf="loading">
14+
<div class="spinner"></div>
15+
</div>
16+
17+
<div *ngIf="!loading" class="no-data-container">
18+
<ng-lottie [options]="lottieOptions" style="width: 200px; height: 200px">
19+
</ng-lottie>
20+
<p>Không có dữ liệu</p>
21+
</div>
22+
</div>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
.saved-posts-container {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 1rem;
5+
padding: 16px;
6+
overflow-y: auto;
7+
height: calc(100vh - 180px);
8+
9+
.saved-post-card {
10+
background: #fff;
11+
border-radius: 12px;
12+
padding: 16px;
13+
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
14+
cursor: pointer;
15+
transition: transform 0.2s ease, box-shadow 0.2s ease;
16+
17+
&:hover {
18+
transform: translateY(-4px);
19+
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.12);
20+
}
21+
22+
.card-content {
23+
display: flex;
24+
flex-direction: column;
25+
26+
.post-title {
27+
font-size: 1.1rem;
28+
font-weight: 600;
29+
color: #222;
30+
margin-bottom: 6px;
31+
}
32+
33+
.save-date {
34+
font-size: 0.85rem;
35+
color: #777;
36+
}
37+
}
38+
}
39+
40+
.loading-spinner {
41+
display: flex;
42+
justify-content: center;
43+
padding: 1rem;
44+
45+
.spinner {
46+
width: 24px;
47+
height: 24px;
48+
border: 3px solid #ddd;
49+
border-top: 3px solid #007bff;
50+
border-radius: 50%;
51+
animation: spin 0.8s linear infinite;
52+
}
53+
}
54+
55+
.no-data-container {
56+
display: flex;
57+
flex-direction: column;
58+
align-items: center;
59+
justify-content: center;
60+
height: 100%;
61+
text-align: center;
62+
padding: 20px;
63+
}
64+
}
65+
66+
@keyframes spin {
67+
to {
68+
transform: rotate(360deg);
69+
}
70+
}

0 commit comments

Comments
 (0)