1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 04:46:40 +07:00

added profile quick info

This commit is contained in:
Fedor Katurov 2022-07-19 16:47:03 +07:00
parent 1241d2c784
commit 141b9c0d60
8 changed files with 84 additions and 32 deletions

View file

@ -16,10 +16,11 @@ interface Props {
const CommentAvatar: FC<Props> = ({ user, className }) => {
return (
<MenuButton
position="top"
position="top-start"
icon={
<Avatar url={path(['photo', 'url'], user)} username={user.username} className={className} />
}
translucentMenu
>
<ProfileQuickInfo user={user} />
</MenuButton>

View file

@ -12,6 +12,7 @@ interface MenuButtonProps {
position?: 'top-end' | 'bottom-end' | 'top-start' | 'bottom-start' | 'top' | 'bottom';
icon?: ReactNode;
className?: string;
translucentMenu?: boolean;
}
const modifiers = [
@ -28,8 +29,9 @@ const MenuButton: FC<MenuButtonProps> = ({
children,
className,
icon = <Icon icon="dots-vertical" size={24} />,
translucentMenu,
}) => {
const { focused, onFocus, onBlur } = useFocusEvent(true, 150);
const { focused, onFocus, onBlur } = useFocusEvent(false, 150);
return (
<Manager>
@ -49,7 +51,13 @@ const MenuButton: FC<MenuButtonProps> = ({
{focused && (
<Popper placement={position} modifiers={modifiers}>
{({ style, ref, placement }) => (
<div style={style} ref={ref} className={classNames(styles.popper, styles[placement])}>
<div
style={style}
ref={ref}
className={classNames(styles.popper, styles[placement], {
[styles.translucent]: translucentMenu,
})}
>
{children}
</div>
)}

View file

@ -5,19 +5,13 @@
@import "src/styles/variables.scss";
@keyframes appear {
0% { opacity: 0 }
100% { opacity: 1 }
}
.popper {
@include outer_shadow;
@include blur($content_bg, 15px, 0.5);
background-color: $menu_bg;
box-sizing: border-box;
z-index: 12;
border-radius: $radius;
animation: appear forwards 250ms;
&::after {
content: ' ';
@ -27,14 +21,27 @@
border-width: 0 10px 10px 10px;
border-color: transparent transparent lighten($menu_bg, 6%) transparent;
position: absolute;
}
&.bottom-end::after {
top: -11px;
right: 10px;
}
&.bottom-start::after {
top: -11px;
left: 10px;
}
&.bottom::after {
top: -11px;
left: 50%;
margin-left: -15px;
}
&.top-end::after {
border-width: 10px 10px 0 10px;
border-color: darken($menu_bg, 8%) transparent transparent transparent;
top: auto;
bottom: -11px;
}
@ -43,7 +50,6 @@
border-color: darken($menu_bg, 8%) transparent transparent transparent;
top: auto;
bottom: -11px;
right: auto;
left: 10px;
}
@ -52,8 +58,7 @@
border-color: darken($menu_bg, 8%) transparent transparent transparent;
top: auto;
bottom: -11px;
right: auto;
left: 50%;
margin-left: -10px;
margin-left: -15px;
}
}

View file

@ -1,9 +1,13 @@
import React, { FC, useMemo } from 'react';
import classNames from 'classnames';
import { Avatar } from '~/components/common/Avatar';
import { Filler } from '~/components/containers/Filler';
import { Group } from '~/components/containers/Group';
import { useRandomPhrase } from '~/constants/phrases';
import { useUserActiveStatus } from '~/hooks/auth/useUserActiveStatus';
import { useUserDescription } from '~/hooks/auth/useUserDescription';
import { useColorGradientFromString } from '~/hooks/color/useColorGradientFromString';
import { IUser } from '~/types/auth';
import { generateGradientFromColor } from '~/utils/color';
@ -16,15 +20,10 @@ interface ProfileQuickInfoProps {
}
const ProfileQuickInfo: FC<ProfileQuickInfoProps> = ({ user }) => {
const style = useMemo(() => {
const color = user.photo?.metadata?.dominant_color;
const gradient = color && generateGradientFromColor(color);
return gradient ? { background: gradient } : undefined;
}, [user]);
const isActive = useUserActiveStatus(user.last_seen);
return (
<Group className={styles.wrapper} style={style}>
<Group className={styles.wrapper}>
<Group className={styles.top} horizontal>
<div>
<Avatar url={path(['photo', 'url'], user)} username={user.username} />
@ -33,6 +32,10 @@ const ProfileQuickInfo: FC<ProfileQuickInfoProps> = ({ user }) => {
<Filler>
<h5 className={styles.fullname}>{user.fullname || user.username}</h5>
<div className={styles.username}>~{user.username}</div>
<div className={classNames(styles.status, { [styles.active]: isActive })}>
{isActive ? 'в сознании' : 'деактивирован'}
</div>
</Filler>
</Group>
</Group>

View file

@ -26,3 +26,19 @@ div.top.top {
.fullname {
margin-bottom: 3px;
}
.status {
font: $font_12_regular;
margin-top: $gap;
&.active {
color: $olive;
}
&::before {
content: '';
margin-right: 5px;
padding-top: 1px;
font-size: 0.8em;
}
}

View file

@ -0,0 +1,14 @@
import { differenceInDays, parseISO } from 'date-fns';
import { INACTIVE_ACCOUNT_DAYS } from '~/constants/user';
const today = new Date();
export const useUserActiveStatus = (lastSeen?: string) => {
try {
const lastSeenDate = lastSeen ? parseISO(lastSeen) : undefined;
return lastSeenDate && differenceInDays(today, lastSeenDate) < INACTIVE_ACCOUNT_DAYS;
} catch (e) {
return false;
}
};

View file

@ -1,11 +1,7 @@
import { differenceInDays, parseISO } from 'date-fns';
import { useRandomPhrase } from '~/constants/phrases';
import { INACTIVE_ACCOUNT_DAYS } from '~/constants/user';
import { useUserActiveStatus } from '~/hooks/auth/useUserActiveStatus';
import { IUser } from '~/types/auth';
const today = new Date();
export const useUserDescription = (user?: Partial<IUser>) => {
const randomPhrase = useRandomPhrase('USER_DESCRIPTION');
@ -13,8 +9,9 @@ export const useUserDescription = (user?: Partial<IUser>) => {
return '';
}
const lastSeen = user.last_seen ? parseISO(user.last_seen) : undefined;
if (!lastSeen || differenceInDays(today, lastSeen) > INACTIVE_ACCOUNT_DAYS) {
const isActive = useUserActiveStatus(user.last_seen);
if (!isActive) {
return 'Юнит деактивирован';
}

View file

@ -1,4 +1,4 @@
import { adjustHue, darken, desaturate, parseToHsla } from 'color2k';
import { adjustHue, darken, desaturate, parseToHsla, transparentize } from 'color2k';
import { DEFAULT_DOMINANT_COLOR } from '~/constants/node';
import { stringToColour } from '~/utils/dom';
@ -30,8 +30,16 @@ export const generateGradientFromColor = (
val: string,
saturation = 3,
lightness = 3,
angle = 155
angle = 155,
opacity = 1
) => {
const [color, second, third] = generateColorTriplet(val, saturation, lightness);
return `linear-gradient(${angle}deg, ${color}, ${second}, ${third})`;
const [first, second, third] = generateColorTriplet(val, saturation, lightness).map(it => {
if (opacity > 1 || opacity < 0) {
return it;
}
return transparentize(it, 1 - opacity);
});
return `linear-gradient(${angle}deg, ${first}, ${second}, ${third})`;
};