Skip to content

Commit

Permalink
fix: loading state of profile (#565)
Browse files Browse the repository at this point in the history
* fix: loading state of profile

* fix: upadte lock file
  • Loading branch information
spaenleh authored Mar 18, 2024
1 parent ef91d54 commit d5c116d
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 180 deletions.
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

0 comments on commit d5c116d

Please sign in to comment.