Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: loading state of profile #565

Merged
merged 2 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@emotion/react": "11.11.4",
"@emotion/server": "11.11.0",
"@emotion/styled": "11.11.0",
"@graasp/query-client": "2.9.1",
"@graasp/query-client": "3.0.1",
"@graasp/sdk": "4.2.1",
"@graasp/translations": "1.25.3",
"@graasp/ui": "4.11.0",
Expand Down
176 changes: 60 additions & 116 deletions src/components/member/BioSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,141 +5,85 @@ import { useContext } from 'react';
import FacebookIcon from '@mui/icons-material/Facebook';
import LinkedInIcon from '@mui/icons-material/LinkedIn';
import TwitterIcon from '@mui/icons-material/Twitter';
import { Link, Skeleton, Stack, Typography } from '@mui/material';
import { IconButton, Link, Skeleton, Stack, Typography } from '@mui/material';

import { Member, ThumbnailSize } from '@graasp/sdk';
import { Avatar } from '@graasp/ui';

import { DEFAULT_MEMBER_THUMBNAIL } from '../../config/constants';
import { useLibraryTranslation } from '../../config/i18n';
import { publicProfileAccountPath } from '../../config/paths';
import LIBRARY from '../../langs/constants';
import { QueryClientContext } from '../QueryClientContext';
import ShowLessAndMoreContent from '../common/ShowLessAndMoreContent';

const socialLinks = new SocialLinks();

interface Props {
id: string;
memberData?: Member;
isOwnProfile: boolean;
}
type Props = {
memberId: string;
};

const loadingIcon = <Skeleton variant="circular" width={40} height={40} />;

const BioSection = ({ id, memberData, isOwnProfile }: Props) => {
const BioSection = ({ memberId }: Props): JSX.Element | null => {
const { t } = useLibraryTranslation();

const { hooks } = useContext(QueryClientContext);
const { data: publicProfile, isInitialLoading: isLoadingPublicProfile } =
hooks.usePublicProfile(id);
const { data: authorUrl, isInitialLoading: isLoadingAuthorAvatar } =
hooks.useAvatarUrl({
id,
size: ThumbnailSize.Small,
});
hooks.usePublicProfile(memberId);

return (
<Stack
maxWidth="lg"
alignItems="flex-start"
justifyItems="flex-start"
marginTop={2}
width="100%"
spacing={3}
>
<Stack
id="memberSection"
padding={2}
width="100%"
spacing={8}
justifyContent={{ sm: 'center', md: 'flex-start' }}
direction={{ sm: 'column', md: 'row' }}
>
<Avatar
alt={t(LIBRARY.AVATAR_ALT, { name: memberData?.name })}
maxWidth={120}
maxHeight={120}
variant="circular"
sx={{
width: 200,
height: 200,
marginTop: 2,
}}
url={authorUrl ?? DEFAULT_MEMBER_THUMBNAIL}
isLoading={isLoadingAuthorAvatar}
component="avatar"
/>
<Stack id="memberData" spacing={2} width="100%" flexGrow={1}>
<Stack
id="memberNameHeader"
direction="row"
alignItems="center"
justifyItems="space-between"
spacing={2}
width="100%"
>
<Typography variant="h3" textTransform="capitalize" flexGrow={1}>
{memberData ? memberData?.name : <Skeleton width="10ch" />}
</Typography>
{isOwnProfile && (
<Link href={publicProfileAccountPath}>{t(LIBRARY.EDIT)}</Link>
)}
</Stack>
{publicProfile?.bio ? (
<ShowLessAndMoreContent content={publicProfile?.bio} />
) : (
<Skeleton variant="rectangular" height="3lh" />
if (publicProfile !== undefined) {
// public profile is not visible and thus the data is null
if (publicProfile == null) {
return null;
}
const { bio, linkedinID, facebookID, twitterID } = publicProfile;
return (
<>
<ShowLessAndMoreContent content={bio} />
{(linkedinID || facebookID || twitterID) && (
<Typography variant="body1" fontWeight="bold">
{t(LIBRARY.SOCIAL_PROFILES)}
</Typography>
)}
<Stack direction="row" spacing={1}>
{facebookID && (
<IconButton
component={Link}
href={socialLinks.sanitize('facebook', facebookID)}
>
<FacebookIcon sx={{ fill: '#4267B2' }} />
</IconButton>
)}
{twitterID && (
<IconButton
component={Link}
href={socialLinks.sanitize('twitter', twitterID)}
>
<TwitterIcon sx={{ fill: '#1DA1F2' }} />
</IconButton>
)}
{(publicProfile?.facebookID ||
publicProfile?.linkedinID ||
publicProfile?.twitterID) && (
<Typography variant="body1" fontWeight="bold">
{t(LIBRARY.SOCIAL_PROFILES)}
</Typography>
{linkedinID && (
<IconButton
component={Link}
href={socialLinks.sanitize('linkedin', linkedinID)}
>
<LinkedInIcon sx={{ fill: '#0077B5' }} />
</IconButton>
)}
<Stack direction="row" spacing={1}>
{isLoadingPublicProfile
? loadingIcon
: publicProfile?.facebookID && (
<Link
href={socialLinks.sanitize(
'facebook',
publicProfile?.facebookID,
)}
>
<FacebookIcon sx={{ fill: '#4267B2' }} />
</Link>
)}
{isLoadingPublicProfile
? loadingIcon
: publicProfile?.twitterID && (
<Link
href={socialLinks.sanitize(
'twitter',
publicProfile?.twitterID,
)}
>
<TwitterIcon sx={{ fill: '#1DA1F2' }} />
</Link>
)}
{isLoadingPublicProfile
? loadingIcon
: publicProfile?.linkedinID && (
<Link
href={socialLinks.sanitize(
'linkedin',
publicProfile?.linkedinID,
)}
>
<LinkedInIcon sx={{ fill: '#0077B5' }} />
</Link>
)}
</Stack>
</Stack>
</Stack>
</Stack>
);
</>
);
}
if (isLoadingPublicProfile) {
return (
<>
<Skeleton variant="rectangular" height="3lh" />
<Stack direction="row" spacing={1}>
{loadingIcon}
{loadingIcon}
{loadingIcon}
</Stack>
</>
);
}

return null;
};

export default BioSection;
87 changes: 87 additions & 0 deletions src/components/member/MemberHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useContext } from 'react';

import { Link, Skeleton, Stack, Typography } from '@mui/material';

import { ThumbnailSize } from '@graasp/sdk';
import { Avatar } from '@graasp/ui';

import {
DEFAULT_MEMBER_THUMBNAIL,
MEMBER_AVATAR_MAIN_SIZE,
} from '../../config/constants';
import { useLibraryTranslation } from '../../config/i18n';
import { publicProfileAccountPath } from '../../config/paths';
import LIBRARY from '../../langs/constants';
import { QueryClientContext } from '../QueryClientContext';
import BioSection from './BioSection';

type Props = {
memberId: string;
isOwnProfile: boolean;
};

const MemberHeader = ({ memberId, isOwnProfile }: Props) => {
const { t } = useLibraryTranslation();

const { hooks } = useContext(QueryClientContext);
const { data: member } = hooks.useMember(memberId);
const { data: authorUrl, isInitialLoading: isLoadingAuthorAvatar } =
hooks.useAvatarUrl({
id: memberId,
size: ThumbnailSize.Medium,
});

return (
<Stack
maxWidth="lg"
alignItems="flex-start"
justifyItems="flex-start"
marginTop={2}
width="100%"
spacing={3}
>
<Stack
id="memberSection"
padding={2}
width="100%"
spacing={8}
justifyContent={{ sm: 'center', md: 'flex-start' }}
direction={{ sm: 'column', md: 'row' }}
>
<Avatar
alt={t(LIBRARY.AVATAR_ALT, { name: member?.name })}
maxWidth={120}
maxHeight={120}
variant="circular"
sx={{
width: MEMBER_AVATAR_MAIN_SIZE,
height: MEMBER_AVATAR_MAIN_SIZE,
}}
url={authorUrl ?? DEFAULT_MEMBER_THUMBNAIL}
isLoading={isLoadingAuthorAvatar}
component="avatar"
/>
<Stack id="memberData" spacing={2} width="100%" flexGrow={1}>
<Stack
id="memberNameHeader"
direction="row"
alignItems="center"
justifyItems="space-between"
spacing={2}
width="100%"
>
<Typography variant="h3" textTransform="capitalize" flexGrow={1}>
{member ? member?.name : <Skeleton width="10ch" />}
</Typography>
{isOwnProfile && (
<Link href={publicProfileAccountPath}>{t(LIBRARY.EDIT)}</Link>
)}
</Stack>
<BioSection memberId={memberId} />
</Stack>
</Stack>
</Stack>
);
};

export default MemberHeader;
19 changes: 9 additions & 10 deletions src/components/pages/Member.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,28 @@ import ItemCollection from '../collection/ItemCollection';
import BackButton from '../common/BackButton';
import Error from '../common/Error';
import MainWrapper from '../layout/MainWrapper';
import BioSection from '../member/BioSection';
import MemberHeader from '../member/MemberHeader';

type Props = {
id: string;
};

const MemberPage = ({ id }: Props) => {
const MemberPage = ({ id: memberId }: Props) => {
const { hooks } = useContext(QueryClientContext);
const { data } = hooks.useMember(id);
const { data: member } = hooks.useCurrentMember();
const { data: memberPublishedItems } = hooks.usePublishedItemsForMember(id);
const { data: memberPublishedItems } =
hooks.usePublishedItemsForMember(memberId);

const { t } = useLibraryTranslation();

if (validate(id)) {
if (validate(memberId)) {
return (
<Stack maxWidth="xl" spacing={3} paddingY={3}>
<Stack direction="column" paddingX={3} alignItems="flex-start">
<BackButton />
<BioSection
memberData={data}
id={id}
isOwnProfile={member?.id === id}
<MemberHeader
memberId={memberId}
isOwnProfile={member?.id === memberId}
/>
</Stack>

Expand All @@ -51,7 +50,7 @@ const MemberPage = ({ id }: Props) => {
}

return (
<Box id={id} p={5}>
<Box id={memberId} p={5}>
<Error code={ERROR_NOT_A_MEMBER_ID} />
</Box>
);
Expand Down
29 changes: 0 additions & 29 deletions src/components/pages/MyFavorites.tsx

This file was deleted.

1 change: 1 addition & 0 deletions src/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const DEFAULT_MEMBER_THUMBNAIL = `data:image/svg+xml,${encodeURIComponent

export const SMALL_AVATAR_ICON_SIZE = 30;
export const MEMBER_AVATAR_ICON_SIZE = 40;
export const MEMBER_AVATAR_MAIN_SIZE = 256;

export const UrlSearch = {
KeywordSearch: 's',
Expand Down
Loading