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 }) => { const CommentAvatar: FC<Props> = ({ user, className }) => {
return ( return (
<MenuButton <MenuButton
position="top" position="top-start"
icon={ icon={
<Avatar url={path(['photo', 'url'], user)} username={user.username} className={className} /> <Avatar url={path(['photo', 'url'], user)} username={user.username} className={className} />
} }
translucentMenu
> >
<ProfileQuickInfo user={user} /> <ProfileQuickInfo user={user} />
</MenuButton> </MenuButton>

View file

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

View file

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

View file

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

View file

@ -26,3 +26,19 @@ div.top.top {
.fullname { .fullname {
margin-bottom: 3px; 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 { useRandomPhrase } from '~/constants/phrases';
import { INACTIVE_ACCOUNT_DAYS } from '~/constants/user'; import { useUserActiveStatus } from '~/hooks/auth/useUserActiveStatus';
import { IUser } from '~/types/auth'; import { IUser } from '~/types/auth';
const today = new Date();
export const useUserDescription = (user?: Partial<IUser>) => { export const useUserDescription = (user?: Partial<IUser>) => {
const randomPhrase = useRandomPhrase('USER_DESCRIPTION'); const randomPhrase = useRandomPhrase('USER_DESCRIPTION');
@ -13,8 +9,9 @@ export const useUserDescription = (user?: Partial<IUser>) => {
return ''; return '';
} }
const lastSeen = user.last_seen ? parseISO(user.last_seen) : undefined; const isActive = useUserActiveStatus(user.last_seen);
if (!lastSeen || differenceInDays(today, lastSeen) > INACTIVE_ACCOUNT_DAYS) {
if (!isActive) {
return 'Юнит деактивирован'; 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 { DEFAULT_DOMINANT_COLOR } from '~/constants/node';
import { stringToColour } from '~/utils/dom'; import { stringToColour } from '~/utils/dom';
@ -30,8 +30,16 @@ export const generateGradientFromColor = (
val: string, val: string,
saturation = 3, saturation = 3,
lightness = 3, lightness = 3,
angle = 155 angle = 155,
opacity = 1
) => { ) => {
const [color, second, third] = generateColorTriplet(val, saturation, lightness); const [first, second, third] = generateColorTriplet(val, saturation, lightness).map(it => {
return `linear-gradient(${angle}deg, ${color}, ${second}, ${third})`; if (opacity > 1 || opacity < 0) {
return it;
}
return transparentize(it, 1 - opacity);
});
return `linear-gradient(${angle}deg, ${first}, ${second}, ${third})`;
}; };