diff --git a/src/components/Friends/FriendProgressItem.tsx b/src/components/Friends/FriendProgressItem.tsx
new file mode 100644
index 000000000..bc07926ef
--- /dev/null
+++ b/src/components/Friends/FriendProgressItem.tsx
@@ -0,0 +1,259 @@
+import { useState } from 'preact/hooks';
+import type { ListFriendsResponse } from './FriendsPage';
+import { DeleteUserIcon } from '../ReactIcons/DeleteUserIcon';
+import { pageProgressMessage } from '../../stores/page';
+import { httpDelete, httpPost } from '../../lib/http';
+import { useToast } from '../../hooks/use-toast';
+import { TrashIcon } from '../ReactIcons/TrashIcon';
+import { AddedUserIcon } from '../ReactIcons/AddedUserIcon';
+import { AddUserIcon } from '../ReactIcons/AddUserIcon';
+
+type FriendProgressItemProps = {
+ friend: ListFriendsResponse[0];
+ onShowResourceProgress: (resourceId: string) => void;
+ onReload: () => void;
+};
+
+export function FriendProgressItem(props: FriendProgressItemProps) {
+ const { friend, onShowResourceProgress, onReload } = props;
+ const toast = useToast();
+
+ async function deleteFriend(userId: string, successMessage: string) {
+ pageProgressMessage.set('Please wait...');
+ const { response, error } = await httpDelete(
+ `${import.meta.env.PUBLIC_API_URL}/v1-delete-friend/${userId}`,
+ {}
+ );
+
+ if (error || !response) {
+ toast.error(error?.message || 'Something went wrong');
+ return;
+ }
+
+ toast.success(successMessage);
+ onReload();
+ }
+
+ async function addFriend(userId: string, successMessage: string) {
+ pageProgressMessage.set('Please wait...');
+ const { response, error } = await httpPost(
+ `${import.meta.env.PUBLIC_API_URL}/v1-add-friend/${userId}`,
+ {}
+ );
+
+ if (error || !response) {
+ toast.error(error?.message || 'Something went wrong');
+ return;
+ }
+
+ toast.success(successMessage);
+ onReload();
+ }
+
+ const roadmaps = (friend.roadmaps || []).sort((a, b) => {
+ return b.done - a.done;
+ });
+
+ const [showAll, setShowAll] = useState(false);
+ const status = friend.status;
+
+ return (
+ <>
+
+
+
+
+
{friend.name}
+
{friend.email}
+
+
+ {friend.status === 'accepted' && (
+
+ {(showAll ? roadmaps : roadmaps.slice(0, 4)).map((progress) => {
+ return (
+
+ );
+ })}
+
+ {roadmaps.length > 4 && !showAll && (
+
+ )}
+
+ {showAll && (
+
+ )}
+
+ {roadmaps.length === 0 && (
+
No progress
+ )}
+
+ )}
+
+ {friend.status === 'rejected' && (
+ <>
+
+
+
+ Request Rejected
+
+
+
+ Changed your mind?{' '}
+
+
+ >
+ )}
+
+ {friend.status === 'got_rejected' && (
+ <>
+
+
+
+ Request Rejected
+
+
+
+
+
+ >
+ )}
+
+ {friend.status === 'sent' && (
+ <>
+
+
+
+
+ >
+ )}
+
+ {friend.status === 'received' && (
+ <>
+
+
+
Request Received
+
+
+
+
+
+ >
+ )}
+
+ >
+ );
+}
diff --git a/src/components/Friends/FriendsPage.tsx b/src/components/Friends/FriendsPage.tsx
index a6b27de6c..305a89996 100644
--- a/src/components/Friends/FriendsPage.tsx
+++ b/src/components/Friends/FriendsPage.tsx
@@ -6,6 +6,8 @@ import { httpGet } from '../../lib/http';
import type { FriendshipStatus } from '../Befriend';
import { useToast } from '../../hooks/use-toast';
import { EmptyFriends } from './EmptyFriends';
+import { FriendProgressItem } from './FriendProgressItem';
+import UserIcon from '../../icons/user.svg';
type FriendResourceProgress = {
updatedAt: string;
@@ -18,18 +20,35 @@ type FriendResourceProgress = {
total: number;
};
-type ListFriendsResponse = {
+export type ListFriendsResponse = {
userId: string;
name: string;
+ email: string;
avatar: string;
status: FriendshipStatus;
roadmaps: FriendResourceProgress[];
bestPractices: FriendResourceProgress[];
}[];
+type GroupingType = {
+ label: string;
+ value: 'active' | 'requests' | 'sent';
+ statuses: FriendshipStatus[];
+};
+
+const groupingTypes: GroupingType[] = [
+ { label: 'Active', value: 'active', statuses: ['accepted'] },
+ { label: 'Requests', value: 'requests', statuses: ['received', 'rejected'] },
+ { label: 'Sent', value: 'sent', statuses: ['sent', 'got_rejected'] },
+];
+
export function FriendsPage() {
const toast = useToast();
+
+ const [isLoading, setIsLoading] = useState(true);
const [friends, setFriends] = useState([]);
+ const [selectedGrouping, setSelectedGrouping] =
+ useState('active');
async function loadFriends() {
const { response, error } = await httpGet(
@@ -47,6 +66,7 @@ export function FriendsPage() {
useEffect(() => {
loadFriends().finally(() => {
pageProgressMessage.set('');
+ setIsLoading(false);
});
}, []);
@@ -60,17 +80,100 @@ export function FriendsPage() {
return ;
}
+ const selectedGroupingType = groupingTypes.find(
+ (grouping) => grouping.value === selectedGrouping
+ );
+
+ const filteredFriends = friends.filter((friend) =>
+ selectedGroupingType?.statuses.includes(friend.status)
+ );
+
+ const receivedRequests = friends.filter(
+ (friend) => friend.status === 'received'
+ );
+
+ if (isLoading) {
+ return null;
+ }
+
+ if (!friends?.length) {
+ return ;
+ }
+
return (
-
- You have 4 active friends
-
+
+ {groupingTypes.map((grouping) => {
+ let requestCount = 0;
+ if (grouping.value === 'requests') {
+ requestCount = receivedRequests.length;
+ }
+
+ return (
+
+ );
+ })}
+
+
+ {filteredFriends.length > 0 && (
+
+ {filteredFriends.map((friend) => (
+ {}}
+ key={friend.userId}
+ onReload={() => {
+ pageProgressMessage.set('Reloading friends ..');
+ loadFriends().finally(() => {
+ pageProgressMessage.set('');
+ });
+ }}
+ />
+ ))}
+
+ )}
+
+ {filteredFriends.length === 0 && (
+
+
+
+ {selectedGrouping === 'active' && 'No friends yet'}
+ {selectedGrouping === 'sent' && 'No requests sent'}
+ {selectedGrouping === 'requests' && 'No requests received'}
+
+
+ Invite your friends to join you on Roadmap
+
+
+
+ )}
);
}