Add friends listing

pull/4317/head
Kamran Ahmed 1 year ago
parent 92295a7906
commit b69889cc29
  1. 20
      src/components/AccountSidebar.astro
  2. 45
      src/components/Friends/FriendProgressItem.tsx
  3. 30
      src/components/Friends/FriendsPage.tsx
  4. 64
      src/components/Friends/InviteFriendPopup.tsx
  5. 8
      src/components/Navigation/AccountDropdown.astro

@ -21,6 +21,16 @@ const sidebarLinks = [
classes: 'h-3 w-4',
},
},
{
href: '/account/friends',
title: 'Friends',
id: 'friends',
isNew: true,
icon: {
glyph: 'users',
classes: 'h-4 w-4',
},
},
{
href: '/account/road-card',
title: 'Card',
@ -31,16 +41,6 @@ const sidebarLinks = [
classes: 'h-4 w-4',
},
},
// {
// href: '/account/friends',
// title: 'Friends',
// id: 'friends',
// isNew: true,
// icon: {
// glyph: 'users',
// classes: 'h-4 w-4',
// },
// },
{
href: '/account/update-profile',
title: 'Profile',

@ -17,6 +17,8 @@ type FriendProgressItemProps = {
export function FriendProgressItem(props: FriendProgressItemProps) {
const { friend, onShowResourceProgress, onReload } = props;
const toast = useToast();
const [isConfirming, setIsConfirming] =
useState<ListFriendsResponse[0]['status']>();
async function deleteFriend(userId: string, successMessage: string) {
pageProgressMessage.set('Please wait...');
@ -79,6 +81,7 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
</div>
</div>
{friend.status === 'accepted' && (
<>
<div className="relative flex grow flex-col space-y-2 p-3">
{(showAll ? roadmaps : roadmaps.slice(0, 4)).map((progress) => {
return (
@ -108,7 +111,7 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
{roadmaps.length > 4 && !showAll && (
<button
onClick={() => setShowAll(true)}
className={'text-sm text-gray-400 underline'}
className={'text-xs text-gray-400 underline'}
>
+ {roadmaps.length - 4} more
</button>
@ -127,6 +130,46 @@ export function FriendProgressItem(props: FriendProgressItemProps) {
<div className="text-sm text-gray-500">No progress</div>
)}
</div>
<>
{isConfirming !== 'accepted' && (
<button
className="flex w-full items-center justify-center border-t py-2 text-sm font-medium text-red-700 hover:bg-red-50/50 hover:text-red-500"
onClick={() => {
setIsConfirming('accepted');
}}
>
<TrashIcon className="mr-1 h-4 w-4" />
Remove Friend
</button>
)}
{isConfirming === 'accepted' && (
<span className="flex w-full items-center justify-center border-t py-2 text-sm text-red-700">
Are you sure?{' '}
<button
className="ml-2 font-medium text-red-700 underline underline-offset-2 hover:text-red-500"
onClick={() => {
deleteFriend(friend.userId, 'Friend removed').finally(
() => {
pageProgressMessage.set('');
}
);
}}
>
Yes
</button>{' '}
<button
className="ml-2 font-medium text-red-700 underline underline-offset-2 hover:text-red-500"
onClick={() => {
setIsConfirming(undefined);
}}
>
No
</button>
</span>
)}
</>
</>
)}
{friend.status === 'rejected' && (

@ -9,6 +9,7 @@ import { EmptyFriends } from './EmptyFriends';
import { FriendProgressItem } from './FriendProgressItem';
import UserIcon from '../../icons/user.svg';
import { UserProgressModal } from '../UserProgress/UserProgressModal';
import { InviteFriendPopup } from './InviteFriendPopup';
type FriendResourceProgress = {
updatedAt: string;
@ -46,6 +47,8 @@ const groupingTypes: GroupingType[] = [
export function FriendsPage() {
const toast = useToast();
const [showInviteFriendPopup, setShowInviteFriendPopup] = useState(false);
const [showFriendProgress, setShowFriendProgress] = useState<{
resourceId: string;
friend: ListFriendsResponse[0];
@ -108,6 +111,13 @@ export function FriendsPage() {
return (
<div>
{showInviteFriendPopup && (
<InviteFriendPopup
befriendUrl={befriendUrl}
onClose={() => setShowInviteFriendPopup(false)}
/>
)}
{showFriendProgress && (
<UserProgressModal
userId={showFriendProgress.friend.userId}
@ -117,7 +127,7 @@ export function FriendsPage() {
/>
)}
<div className="mb-4 flex items-center justify-between">
<div className="mb-4 flex flex-col items-stretch justify-between gap-2 sm:flex-row sm:items-center sm:gap-0">
<div className="flex items-center gap-2">
{groupingTypes.map((grouping) => {
let requestCount = 0;
@ -131,7 +141,7 @@ export function FriendsPage() {
selectedGrouping === grouping.value
? ' border-gray-400 bg-gray-200 '
: ''
}`}
} w-full sm:w-auto`}
onClick={() => setSelectedGrouping(grouping.value)}
>
{grouping.label}
@ -144,14 +154,19 @@ export function FriendsPage() {
);
})}
</div>
<button class="flex items-center justify-center gap-1.5 rounded-md border border-gray-400 bg-gray-50 p-1 px-2 text-sm hover:border-gray-500 hover:bg-gray-100">
<button
onClick={() => {
setShowInviteFriendPopup(true);
}}
class="flex items-center justify-center gap-1.5 rounded-md border border-gray-400 bg-gray-50 p-1 px-2 text-sm hover:border-gray-500 hover:bg-gray-100"
>
<AddUserIcon additionalClasses="w-4 h-4" />
Invite Friends
</button>
</div>
{filteredFriends.length > 0 && (
<div className="grid grid-cols-2 gap-4">
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
{filteredFriends.map((friend) => (
<FriendProgressItem
friend={friend}
@ -188,7 +203,12 @@ export function FriendsPage() {
<p className="text-sm text-gray-500">
Invite your friends to join you on Roadmap
</p>
<button className="mt-4 flex items-center justify-center gap-1.5 rounded-md border border-gray-400 bg-gray-50 p-1 px-2 text-sm hover:border-gray-500 hover:bg-gray-100">
<button
onClick={() => {
setShowInviteFriendPopup(true);
}}
className="mt-4 flex items-center justify-center gap-1.5 rounded-md border border-gray-400 bg-gray-50 p-1 px-2 text-sm hover:border-gray-500 hover:bg-gray-100"
>
<AddUserIcon additionalClasses="w-4 h-4" />
Invite Friends
</button>

@ -0,0 +1,64 @@
import { useEffect, useRef } from 'preact/hooks';
import { useOutsideClick } from '../../hooks/use-outside-click';
import CopyIcon from '../../icons/copy.svg';
import { useCopyText } from '../../hooks/use-copy-text';
type InviteFriendPopupProps = {
befriendUrl: string;
onClose: () => void;
};
export function InviteFriendPopup(props: InviteFriendPopupProps) {
const { onClose, befriendUrl } = props;
const { isCopied, copyText } = useCopyText();
const popupBodyRef = useRef<HTMLDivElement>(null);
const handleClosePopup = () => {
onClose();
};
useOutsideClick(popupBodyRef, handleClosePopup);
return (
<div class="popup fixed left-0 right-0 top-0 z-50 flex h-full items-center justify-center overflow-y-auto overflow-x-hidden bg-black/50">
<div class="relative h-full w-full max-w-md p-4 md:h-auto">
<div
ref={popupBodyRef}
class="popup-body relative rounded-lg bg-white p-4 shadow"
>
<h3 class="mb-1.5 text-xl font-medium sm:text-2xl">Invite URL</h3>
<p className="mb-3 hidden text-sm leading-none text-gray-400 sm:block">
Share the link below with your friends to invite them.
</p>
<div className="mt-4 flex flex-col gap-2 sm:mt-4">
<input
readOnly={true}
className="mt-2 block w-full rounded-md border border-gray-300 px-3 py-2 outline-none placeholder:text-gray-400 focus:border-gray-400"
value={befriendUrl}
onClick={(e) => {
e?.target?.select();
copyText(befriendUrl);
}}
/>
<button
class={`flex items-center justify-center gap-1 rounded-md border-0 px-3 py-2.5 text-sm text-black ${
isCopied
? 'bg-green-300 hover:bg-green-300'
: 'bg-gray-200 hover:bg-gray-300'
}`}
onClick={() => {
copyText(befriendUrl);
}}
>
<img src={CopyIcon} className="h-4 w-4" alt="Invite Friends" />
{isCopied ? 'Copied' : 'Copy URL'}
</button>
</div>
</div>
</div>
</div>
);
}

@ -30,6 +30,14 @@ import Icon from '../AstroIcon.astro';
Profile
</a>
</li>
<li class='px-1'>
<a
href='/account/friends'
class='block rounded px-4 py-2 text-sm font-medium text-slate-100 hover:bg-slate-700'
>
Friends
</a>
</li>
<li class='px-1'>
<a
href='/team'

Loading…
Cancel
Save