Skip to content

Commit

Permalink
fix(UserPic): show colored circle by default
Browse files Browse the repository at this point in the history
  • Loading branch information
DenisVorop committed Aug 29, 2023
1 parent 15eb911 commit e080bd0
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 72 deletions.
26 changes: 26 additions & 0 deletions src/components/Circle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import styled from 'styled-components';

import { stringToColor, isColorDark } from '../utils/stringToColor';

export const Circle = styled.div<{ size: number; str: string }>`
border-radius: 100%;
${({ size, str }) => {
const mainColor = stringToColor(str);
return {
width: `${size}px`,
height: `${size}px`,
background: `linear-gradient(90deg,${mainColor},${stringToColor(str.toUpperCase())})`,
color: `${isColorDark(mainColor) ? 'white' : 'black'}`,
fontSize: `calc(${size / 2}px - 2px)`,
};
}};
display: flex;
align-items: center;
justify-content: center;
letter-spacing: -1.5px;
font-weight: 600;
user-select: none;
`;
43 changes: 21 additions & 22 deletions src/components/Gravatar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import React, { FC, useCallback, useEffect, useRef } from 'react';
import React, { FC, useState } from 'react';
import md5Hash from 'md5';
import styled from 'styled-components';

import { isRetina } from '../utils/isRetina';
import { useMounted } from '../hooks';
import { preloadImage } from '../utils/preloadImage';
import { getInitials } from '../utils/getInitials';

import { Circle } from './Circle';

interface GravatarProps extends React.HTMLAttributes<HTMLImageElement> {
email?: string | null;
Expand All @@ -13,8 +16,8 @@ interface GravatarProps extends React.HTMLAttributes<HTMLImageElement> {
def?: string;
className?: string;
domain?: string;
name?: string | null;

onLoadError?: () => void;
onClick?: () => void;
}

Expand All @@ -30,13 +33,11 @@ export const Gravatar: FC<GravatarProps> = ({
domain = process.env.NEXT_PUBLIC_GRAVATAR_HOST || 'www.gravatar.com',
email,
md5,
onLoadError,
name,
...props
}) => {
const mounted = useMounted();
const imgRef = useRef<HTMLImageElement>(null);

const base = `//${domain}/avatar/`;
const [isError, setIsError] = useState(false);
const [isLoad, setIsLoad] = useState(false);

const query = new URLSearchParams({
s: String(size),
Expand All @@ -52,14 +53,6 @@ export const Gravatar: FC<GravatarProps> = ({

const formattedEmail = email?.trim().toLowerCase() || '';

const onError: React.ReactEventHandler<HTMLImageElement> = useCallback(
({ currentTarget }) => {
currentTarget.onerror = null;
onLoadError?.();
},
[onLoadError],
);

let hash;
if (md5) {
hash = md5;
Expand All @@ -71,14 +64,20 @@ export const Gravatar: FC<GravatarProps> = ({
return <script />;
}

const base = `//${domain}/avatar/`;
const src = `${base}${hash}?${query}`;
const retinaSrc = `${base}${hash}?${retinaQuery}`;
const img = isRetina() ? retinaSrc : src;

useEffect(() => {
if (imgRef.current && mounted) {
imgRef.current.src = isRetina() ? retinaSrc : src;
}
}, [imgRef, mounted, src, retinaSrc]);
preloadImage(img)
.then(() => setIsLoad?.(true))
.catch(() => setIsError?.(true));

return <StyledImage ref={imgRef} height={size} width={size} onError={onError} {...props} />;
return !isLoad || isError ? (
<Circle size={size} str={`${email}`} {...props}>
{getInitials(name)}
</Circle>
) : (
<StyledImage src={img} height={size} width={size} {...props} />
);
};
63 changes: 13 additions & 50 deletions src/components/UserPic.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useCallback, useState } from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';

import { stringToColor, isColorDark } from '../utils/stringToColor';
import { preloadImage } from '../utils/preloadImage';
import { getInitials } from '../utils/getInitials';

import { Gravatar } from './Gravatar';
import { Circle } from './Circle';

interface UserPicProps extends React.HTMLAttributes<HTMLImageElement> {
src?: string | null;
Expand All @@ -20,62 +22,23 @@ const StyledImage = styled.img`
border-radius: 100%;
`;

const Circle = styled.div<{ size: number; str: string }>`
border-radius: 100%;
${({ size, str }) => {
const mainColor = stringToColor(str);
return {
width: `${size}px`,
height: `${size}px`,
background: `linear-gradient(90deg,${mainColor},${stringToColor(str.toUpperCase())})`,
color: `${isColorDark(mainColor) ? 'white' : 'black'}`,
fontSize: `calc(${size / 2}px - 2px)`,
};
}};
display: flex;
align-items: center;
justify-content: center;
letter-spacing: -1.5px;
font-weight: 600;
user-select: none;
`;

const getInitials = (fullName?: string | null) => {
if (!fullName) return '';
const arr = fullName.split(' ');
return `${arr[0]?.[0] || ''} ${arr[1]?.[0] || ''}`;
};

export const UserPic: React.FC<UserPicProps> = ({ src, name, email, size = 32, ...props }) => {
const [isError, setIsError] = useState(false);
const sizePx = `${size}px`;

const onLoadError = useCallback(() => {
setIsError(true);
}, []);
const [isLoad, setIsLoad] = useState(false);

const onError: React.ReactEventHandler<HTMLImageElement> = useCallback(
({ currentTarget }) => {
currentTarget.onerror = null;
onLoadError();
},
[onLoadError],
);
if (src) {
preloadImage(src)
.then(() => setIsLoad(true))
.catch(() => setIsError(true));

if (isError) {
return (
return !isLoad || isError ? (
<Circle size={size} str={`${email}`} {...props}>
{getInitials(name)}
</Circle>
) : (
<StyledImage src={src} height={size} width={size} {...props} />
);
}

if (src) {
return <StyledImage src={src} height={sizePx} width={sizePx} onError={onError} {...props} />;
}

return <Gravatar onLoadError={onLoadError} email={email} size={size} {...props} />;
return <Gravatar name={name} email={email} size={size} {...props} />;
};
5 changes: 5 additions & 0 deletions src/utils/getInitials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const getInitials = (fullName?: string | null) => {
if (!fullName) return '';
const arr = fullName.split(' ');
return `${arr[0]?.[0] || ''} ${arr[1]?.[0] || ''}`;
};
15 changes: 15 additions & 0 deletions src/utils/preloadImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const preloadImage = (src: string) => {
return new Promise((resolve, reject) => {
const img = new Image();

img.onload = () => {
resolve(img);
};

img.onerror = (error) => {
reject(error);
};

img.src = src;
});
};

0 comments on commit e080bd0

Please sign in to comment.