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:
parent
1241d2c784
commit
141b9c0d60
8 changed files with 84 additions and 32 deletions
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
14
src/hooks/auth/useUserActiveStatus.ts
Normal file
14
src/hooks/auth/useUserActiveStatus.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -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 'Юнит деактивирован';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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})`;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue