From 0a5eeae68c080eb5655967843b644a92aaf9232a Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Mon, 7 Aug 2023 18:02:25 +0100 Subject: [PATCH] Add friend page --- .../AuthenticationFlow/GitHubButton.tsx | 2 +- .../AuthenticationFlow/GoogleButton.tsx | 2 +- .../AuthenticationFlow/LinkedInButton.tsx | 2 +- src/components/Befriend.tsx | 226 ++++++++++++++++++ src/components/Friends/EmptyFriends.tsx | 24 +- src/components/Friends/FriendsPage.tsx | 11 +- src/components/ReactIcons/DeleteUserIcon.tsx | 27 +++ src/components/Toast.tsx | 1 + src/pages/befriend.astro | 14 ++ 9 files changed, 291 insertions(+), 18 deletions(-) create mode 100644 src/components/Befriend.tsx create mode 100644 src/components/ReactIcons/DeleteUserIcon.tsx create mode 100644 src/pages/befriend.astro diff --git a/src/components/AuthenticationFlow/GitHubButton.tsx b/src/components/AuthenticationFlow/GitHubButton.tsx index 4b0377490..03b4ffb25 100644 --- a/src/components/AuthenticationFlow/GitHubButton.tsx +++ b/src/components/AuthenticationFlow/GitHubButton.tsx @@ -91,7 +91,7 @@ export function GitHubButton(props: GitHubButtonProps) { // the user was on before they clicked the social login button if (!['/login', '/signup'].includes(window.location.pathname)) { const pagePath = - window.location.pathname === '/respond-invite' + ['/respond-invite', '/befriend'].includes(window.location.pathname) ? window.location.pathname + window.location.search : window.location.pathname; diff --git a/src/components/AuthenticationFlow/GoogleButton.tsx b/src/components/AuthenticationFlow/GoogleButton.tsx index 81715ac05..9c91349e7 100644 --- a/src/components/AuthenticationFlow/GoogleButton.tsx +++ b/src/components/AuthenticationFlow/GoogleButton.tsx @@ -86,7 +86,7 @@ export function GoogleButton(props: GoogleButtonProps) { // the user was on before they clicked the social login button if (!['/login', '/signup'].includes(window.location.pathname)) { const pagePath = - window.location.pathname === '/respond-invite' + ['/respond-invite', '/befriend'].includes(window.location.pathname) ? window.location.pathname + window.location.search : window.location.pathname; diff --git a/src/components/AuthenticationFlow/LinkedInButton.tsx b/src/components/AuthenticationFlow/LinkedInButton.tsx index b378d79a0..c92ec7550 100644 --- a/src/components/AuthenticationFlow/LinkedInButton.tsx +++ b/src/components/AuthenticationFlow/LinkedInButton.tsx @@ -86,7 +86,7 @@ export function LinkedInButton(props: LinkedInButtonProps) { // the user was on before they clicked the social login button if (!['/login', '/signup'].includes(window.location.pathname)) { const pagePath = - window.location.pathname === '/respond-invite' + ['/respond-invite', '/befriend'].includes(window.location.pathname) ? window.location.pathname + window.location.search : window.location.pathname; diff --git a/src/components/Befriend.tsx b/src/components/Befriend.tsx new file mode 100644 index 000000000..9c959df46 --- /dev/null +++ b/src/components/Befriend.tsx @@ -0,0 +1,226 @@ +import { useEffect, useState } from 'preact/hooks'; +import { httpDelete, httpGet, httpPatch, httpPost } from '../lib/http'; +import ErrorIcon from '../icons/error.svg'; +import { pageProgressMessage } from '../stores/page'; +import { isLoggedIn } from '../lib/jwt'; +import { showLoginPopup } from '../lib/popup'; +import { getUrlParams } from '../lib/browser'; +import { CheckIcon } from './ReactIcons/CheckIcon'; +import { DeleteUserIcon } from './ReactIcons/DeleteUserIcon'; +import { useToast } from '../hooks/use-toast'; +import { useAuth } from '../hooks/use-auth'; + +export type FriendshipStatus = + | 'none' + | 'sent' + | 'received' + | 'accepted' + | 'rejected' + | 'got_rejected'; + +type UserResponse = { + id: string; + links: Record; + avatar: string; + name: string; + status: FriendshipStatus; +}; + +export function Befriend() { + const { u: inviteId } = getUrlParams(); + + const toast = useToast(); + const currentUser = useAuth(); + + const [isConfirming, setIsConfirming] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(''); + const [user, setUser] = useState(); + const isAuthenticated = isLoggedIn(); + + async function loadUser(userId: string) { + const { response, error } = await httpGet( + `${import.meta.env.PUBLIC_API_URL}/v1-get-friend/${userId}` + ); + if (error || !response) { + setError(error?.message || 'Something went wrong'); + return; + } + + setUser(response); + } + + useEffect(() => { + if (inviteId) { + loadUser(inviteId).finally(() => { + pageProgressMessage.set(''); + setIsLoading(false); + }); + } else { + setIsLoading(false); + setError('Missing invite ID in URL'); + pageProgressMessage.set(''); + } + }, [inviteId]); + + async function addFriend(userId: string, successMessage: string) { + pageProgressMessage.set('Please wait...'); + setError(''); + const { response, error } = await httpPost( + `${import.meta.env.PUBLIC_API_URL}/v1-add-friend/${userId}`, + {} + ); + + if (error || !response) { + setError(error?.message || 'Something went wrong'); + return; + } + + setUser(response); + toast.success(successMessage); + } + + async function deleteFriend(userId: string, successMessage: string) { + pageProgressMessage.set('Please wait...'); + setError(''); + const { response, error } = await httpDelete( + `${import.meta.env.PUBLIC_API_URL}/v1-delete-friend/${userId}`, + {} + ); + + if (error || !response) { + setError(error?.message || 'Something went wrong'); + return; + } + + setUser(response); + toast.success(successMessage); + } + + if (isLoading) { + return null; + } + + if (!user) { + return ( +
+ {'error'} + +

Error

+

+ {error || 'There was a problem, please try again.'} +

+ + +
+ ); + } + + const userAvatar = user.avatar + ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${user.avatar}` + : '/images/default-avatar.png'; + + const isMe = currentUser?.id === user.id; + + return ( +
+ {'join + +

{user.name}

+

+ After you add {user.name} as a friend, you will be able to view each + other's skills and progress. +

+ +
+
+ {user.status === 'none' && ( + + )} + + {user.status === 'sent' && ( + <> + + + Request Sent + + + {!isConfirming && ( + + )} + + {isConfirming && ( + + Are you sure?{' '} + {' '} + + + )} + + )} +
+
+ + {error && ( +

{error}

+ )} +
+ ); +} diff --git a/src/components/Friends/EmptyFriends.tsx b/src/components/Friends/EmptyFriends.tsx index 1c9fdc0f0..e29261851 100644 --- a/src/components/Friends/EmptyFriends.tsx +++ b/src/components/Friends/EmptyFriends.tsx @@ -1,27 +1,29 @@ import UserPlusIcon from '../../icons/user-plus.svg'; import CopyIcon from '../../icons/copy.svg'; -import { useAuth } from '../../hooks/use-auth'; import { useCopyText } from '../../hooks/use-copy-text'; -export function EmptyFriends() { - const user = useAuth(); +type EmptyFriendsProps = { + befriendUrl: string; +}; + +export function EmptyFriends(props: EmptyFriendsProps) { + const { befriendUrl } = props; const { isCopied, copyText } = useCopyText(); - const befriendUrl = `https://roadmap.sh/befriend?u=${user?.id}`; return (
-
+
no friends

Invite your Friends

-

- Share the link below with your friends to invite them +

+ Invite your friends to join you on your learning journey.

-
+
{ e.currentTarget.select(); @@ -33,7 +35,11 @@ export function EmptyFriends() { readonly />