mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 12:56:41 +07:00
refactored component errors
This commit is contained in:
parent
7031084b09
commit
d4c2e7ee09
79 changed files with 573 additions and 462 deletions
|
@ -1,9 +1,9 @@
|
|||
import React, { FC, MouseEventHandler, ReactElement, useEffect, useRef } from "react";
|
||||
import styles from "./styles.module.scss";
|
||||
import { clearAllBodyScrollLocks, disableBodyScroll } from "body-scroll-lock";
|
||||
import { Icon } from "~/components/input/Icon";
|
||||
import { LoaderCircle } from "~/components/input/LoaderCircle";
|
||||
import { useCloseOnEscape } from "~/utils/hooks";
|
||||
import React, { FC, MouseEventHandler, ReactElement, useEffect, useRef } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { clearAllBodyScrollLocks, disableBodyScroll } from 'body-scroll-lock';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import { LoaderCircle } from '~/components/input/LoaderCircle';
|
||||
import { useCloseOnEscape } from '~/utils/hooks';
|
||||
|
||||
interface IProps {
|
||||
children: React.ReactChild;
|
||||
|
@ -14,7 +14,7 @@ interface IProps {
|
|||
width?: number;
|
||||
error?: string;
|
||||
is_loading?: boolean;
|
||||
overlay?: ReactElement;
|
||||
overlay?: JSX.Element;
|
||||
|
||||
onOverlayClick?: MouseEventHandler<HTMLDivElement>;
|
||||
onRefCapture?: (ref: any) => void;
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import React, { createElement, FC, FormEvent, useCallback, useEffect, useState } from 'react';
|
||||
import React, {
|
||||
createElement,
|
||||
FC,
|
||||
FormEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { IDialogProps } from '~/redux/modal/constants';
|
||||
import { useCloseOnEscape } from '~/utils/hooks';
|
||||
|
@ -16,6 +24,7 @@ import { EMPTY_NODE, NODE_EDITORS } from '~/redux/node/constants';
|
|||
import { BetterScrollDialog } from '../BetterScrollDialog';
|
||||
import { CoverBackdrop } from '~/components/containers/CoverBackdrop';
|
||||
import { IEditorComponentProps } from '~/redux/node/types';
|
||||
import { has, values } from 'ramda';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { editor, errors } = selectNode(state);
|
||||
|
@ -32,7 +41,7 @@ const mapDispatchToProps = {
|
|||
type IProps = IDialogProps &
|
||||
ReturnType<typeof mapStateToProps> &
|
||||
typeof mapDispatchToProps & {
|
||||
type: keyof typeof NODE_EDITORS;
|
||||
type: string;
|
||||
};
|
||||
|
||||
const EditorDialogUnconnected: FC<IProps> = ({
|
||||
|
@ -44,7 +53,7 @@ const EditorDialogUnconnected: FC<IProps> = ({
|
|||
type,
|
||||
}) => {
|
||||
const [data, setData] = useState(EMPTY_NODE);
|
||||
const [temp, setTemp] = useState([]);
|
||||
const [temp, setTemp] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => setData(editor), [editor]);
|
||||
|
||||
|
@ -93,9 +102,18 @@ const EditorDialogUnconnected: FC<IProps> = ({
|
|||
|
||||
useCloseOnEscape(onRequestClose);
|
||||
|
||||
const error = errors && Object.values(errors)[0];
|
||||
const error = values(errors)[0];
|
||||
const component = useMemo(() => {
|
||||
if (!has(type, NODE_EDITORS)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(NODE_EDITORS, type)) return null;
|
||||
return NODE_EDITORS[type];
|
||||
}, [type]);
|
||||
|
||||
if (!component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmit} className={styles.form}>
|
||||
|
@ -107,7 +125,7 @@ const EditorDialogUnconnected: FC<IProps> = ({
|
|||
onClose={onRequestClose}
|
||||
>
|
||||
<div className={styles.editor}>
|
||||
{createElement(NODE_EDITORS[type], {
|
||||
{createElement(component, {
|
||||
data,
|
||||
setData,
|
||||
temp,
|
||||
|
|
|
@ -80,7 +80,7 @@ const LoginDialogUnconnected: FC<IProps> = ({
|
|||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) userSetLoginError(null);
|
||||
if (error) userSetLoginError('');
|
||||
}, [username, password]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -3,9 +3,10 @@ import { Button } from '~/components/input/Button';
|
|||
import { Grid } from '~/components/containers/Grid';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import styles from './styles.module.scss';
|
||||
import { ISocialProvider } from '~/redux/auth/types';
|
||||
|
||||
interface IProps {
|
||||
openOauthWindow: (provider: string) => MouseEventHandler;
|
||||
openOauthWindow: (provider: ISocialProvider) => MouseEventHandler;
|
||||
}
|
||||
|
||||
const LoginDialogButtons: FC<IProps> = ({ openOauthWindow }) => (
|
||||
|
|
|
@ -24,7 +24,7 @@ const ModalUnconnected: FC<IProps> = ({
|
|||
}) => {
|
||||
const onRequestClose = useCallback(() => {
|
||||
modalSetShown(false);
|
||||
modalSetDialog(null);
|
||||
modalSetDialog('');
|
||||
}, [modalSetShown, modalSetDialog]);
|
||||
|
||||
if (!dialog || !DIALOG_CONTENT[dialog] || !is_shown) return null;
|
||||
|
@ -43,10 +43,7 @@ const ModalUnconnected: FC<IProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const Modal = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ModalUnconnected);
|
||||
const Modal = connect(mapStateToProps, mapDispatchToProps)(ModalUnconnected);
|
||||
|
||||
export { ModalUnconnected, Modal };
|
||||
|
||||
|
|
|
@ -78,7 +78,9 @@ const PhotoSwipeUnconnected: FC<Props> = ({ photoswipe, modalSetShown }) => {
|
|||
|
||||
useEffect(() => {
|
||||
window.location.hash = 'preview';
|
||||
return () => (window.location.hash = '');
|
||||
return () => {
|
||||
window.location.hash = '';
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { FC, useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { IDialogProps } from '~/redux/types';
|
||||
import { connect } from 'react-redux';
|
||||
import { BetterScrollDialog } from '../BetterScrollDialog';
|
||||
|
@ -49,7 +49,7 @@ const RestorePasswordDialogUnconnected: FC<IProps> = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (error || is_succesfull) {
|
||||
authSetRestore({ error: null, is_succesfull: false });
|
||||
authSetRestore({ error: '', is_succesfull: false });
|
||||
}
|
||||
}, [password, password_again]);
|
||||
|
||||
|
@ -69,7 +69,7 @@ const RestorePasswordDialogUnconnected: FC<IProps> = ({
|
|||
<Icon icon="check" size={64} />
|
||||
|
||||
<div>Пароль обновлен</div>
|
||||
<div>Добро пожаловать домой, ~{user.username}!</div>
|
||||
<div>Добро пожаловать домой, ~{user?.username}!</div>
|
||||
|
||||
<div />
|
||||
|
||||
|
@ -77,14 +77,16 @@ const RestorePasswordDialogUnconnected: FC<IProps> = ({
|
|||
Ура!
|
||||
</Button>
|
||||
</Group>
|
||||
) : null,
|
||||
) : (
|
||||
undefined
|
||||
),
|
||||
[is_succesfull]
|
||||
);
|
||||
|
||||
const not_ready = useMemo(() => (is_loading && !user ? <div className={styles.shade} /> : null), [
|
||||
is_loading,
|
||||
user,
|
||||
]);
|
||||
const not_ready = useMemo(
|
||||
() => (is_loading && !user ? <div className={styles.shade} /> : undefined),
|
||||
[is_loading, user]
|
||||
);
|
||||
|
||||
const invalid_code = useMemo(
|
||||
() =>
|
||||
|
@ -100,7 +102,9 @@ const RestorePasswordDialogUnconnected: FC<IProps> = ({
|
|||
Очень жаль
|
||||
</Button>
|
||||
</Group>
|
||||
) : null,
|
||||
) : (
|
||||
undefined
|
||||
),
|
||||
[is_loading, user, error]
|
||||
);
|
||||
|
||||
|
@ -135,7 +139,7 @@ const RestorePasswordDialogUnconnected: FC<IProps> = ({
|
|||
type="password"
|
||||
value={password_again}
|
||||
handler={setPasswordAgain}
|
||||
error={password_again && doesnt_match && ERROR_LITERAL[ERRORS.DOESNT_MATCH]}
|
||||
error={password_again && doesnt_match ? ERROR_LITERAL[ERRORS.DOESNT_MATCH] : ''}
|
||||
/>
|
||||
|
||||
<Group className={styles.text}>
|
||||
|
|
|
@ -43,7 +43,7 @@ const RestoreRequestDialogUnconnected: FC<IProps> = ({
|
|||
|
||||
useEffect(() => {
|
||||
if (error || is_succesfull) {
|
||||
authSetRestore({ error: null, is_succesfull: false });
|
||||
authSetRestore({ error: '', is_succesfull: false });
|
||||
}
|
||||
}, [field]);
|
||||
|
||||
|
@ -72,7 +72,9 @@ const RestoreRequestDialogUnconnected: FC<IProps> = ({
|
|||
Отлично!
|
||||
</Button>
|
||||
</Group>
|
||||
) : null,
|
||||
) : (
|
||||
undefined
|
||||
),
|
||||
[is_succesfull]
|
||||
);
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ const BorisLayout: FC<IProps> = () => {
|
|||
|
||||
if (
|
||||
user.last_seen_boris &&
|
||||
last_comment.created_at &&
|
||||
!isBefore(new Date(user.last_seen_boris), new Date(last_comment.created_at))
|
||||
)
|
||||
return;
|
||||
|
|
|
@ -12,9 +12,14 @@ import { NodeNoComments } from '~/components/node/NodeNoComments';
|
|||
import { NodeRelated } from '~/components/node/NodeRelated';
|
||||
import { NodeComments } from '~/components/node/NodeComments';
|
||||
import { NodeTags } from '~/components/node/NodeTags';
|
||||
import { INodeComponentProps, NODE_COMPONENTS, NODE_HEADS, NODE_INLINES } from '~/redux/node/constants';
|
||||
import {
|
||||
INodeComponentProps,
|
||||
NODE_COMPONENTS,
|
||||
NODE_HEADS,
|
||||
NODE_INLINES,
|
||||
} from '~/redux/node/constants';
|
||||
import { selectUser } from '~/redux/auth/selectors';
|
||||
import { pick } from 'ramda';
|
||||
import { path, pick, prop } from 'ramda';
|
||||
import { NodeRelatedPlaceholder } from '~/components/node/NodeRelated/placeholder';
|
||||
import { NodeDeletedBadge } from '~/components/node/NodeDeletedBadge';
|
||||
import { NodeCommentForm } from '~/components/node/NodeCommentForm';
|
||||
|
@ -71,9 +76,6 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
|||
nodeStar,
|
||||
nodeLock,
|
||||
nodeSetCoverImage,
|
||||
nodeLockComment,
|
||||
nodeEditComment,
|
||||
nodeLoadMoreComments,
|
||||
modalShowPhotoswipe,
|
||||
}) => {
|
||||
const [layout, setLayout] = useState({});
|
||||
|
@ -84,7 +86,6 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
|||
comments = [],
|
||||
current: node,
|
||||
related,
|
||||
comment_data,
|
||||
comment_count,
|
||||
} = useShallowSelect(selectNode);
|
||||
const updateLayout = useCallback(() => setLayout({}), []);
|
||||
|
@ -103,6 +104,10 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
|||
|
||||
const onTagClick = useCallback(
|
||||
(tag: Partial<ITag>) => {
|
||||
if (!node?.id || !tag?.title) {
|
||||
return;
|
||||
}
|
||||
|
||||
history.push(URLS.NODE_TAG_URL(node.id, encodeURIComponent(tag.title)));
|
||||
},
|
||||
[history, node.id]
|
||||
|
@ -112,9 +117,9 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
|||
const can_like = useMemo(() => canLikeNode(node, user), [node, user]);
|
||||
const can_star = useMemo(() => canStarNode(node, user), [node, user]);
|
||||
|
||||
const head = node && node.type && NODE_HEADS[node.type];
|
||||
const block = node && node.type && NODE_COMPONENTS[node.type];
|
||||
const inline = node && node.type && NODE_INLINES[node.type];
|
||||
const head = useMemo(() => node?.type && prop(node?.type, NODE_HEADS), [node.type]);
|
||||
const block = useMemo(() => node?.type && prop(node?.type, NODE_COMPONENTS), [node.type]);
|
||||
const inline = useMemo(() => node?.type && prop(node?.type, NODE_INLINES), [node.type]);
|
||||
|
||||
const onEdit = useCallback(() => nodeEdit(node.id), [nodeEdit, node]);
|
||||
const onLike = useCallback(() => nodeLike(node.id), [nodeLike, node]);
|
||||
|
@ -147,10 +152,10 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
|||
|
||||
return (
|
||||
<>
|
||||
{createNodeBlock(head)}
|
||||
{!!head && createNodeBlock(head)}
|
||||
|
||||
<Card className={styles.node} seamless>
|
||||
{createNodeBlock(block)}
|
||||
{!!block && createNodeBlock(block)}
|
||||
|
||||
<NodePanel
|
||||
node={pick(
|
||||
|
@ -208,12 +213,13 @@ const NodeLayoutUnconnected: FC<IProps> = memo(
|
|||
{!is_loading &&
|
||||
related &&
|
||||
related.albums &&
|
||||
!!node?.id &&
|
||||
Object.keys(related.albums)
|
||||
.filter(album => related.albums[album].length > 0)
|
||||
.map(album => (
|
||||
<NodeRelated
|
||||
title={
|
||||
<Link to={URLS.NODE_TAG_URL(node.id, encodeURIComponent(album))}>
|
||||
<Link to={URLS.NODE_TAG_URL(node.id!, encodeURIComponent(album))}>
|
||||
{album}
|
||||
</Link>
|
||||
}
|
||||
|
|
|
@ -1,43 +1,42 @@
|
|||
import React, { FC, useCallback, useEffect, useState } from "react";
|
||||
import styles from "./styles.module.scss";
|
||||
import { connect } from "react-redux";
|
||||
import { getURL } from "~/utils/dom";
|
||||
import { pick } from "ramda";
|
||||
import { selectAuthProfile, selectAuthUser } from "~/redux/auth/selectors";
|
||||
import { PRESETS } from "~/constants/urls";
|
||||
import { selectUploads } from "~/redux/uploads/selectors";
|
||||
import { IFileWithUUID } from "~/redux/types";
|
||||
import uuid from "uuid4";
|
||||
import { UPLOAD_SUBJECTS, UPLOAD_TARGETS, UPLOAD_TYPES } from "~/redux/uploads/constants";
|
||||
import React, { FC, useCallback, useEffect, useState } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { connect } from 'react-redux';
|
||||
import { getURL } from '~/utils/dom';
|
||||
import { pick } from 'ramda';
|
||||
import { selectAuthProfile, selectAuthUser } from '~/redux/auth/selectors';
|
||||
import { PRESETS } from '~/constants/urls';
|
||||
import { selectUploads } from '~/redux/uploads/selectors';
|
||||
import { IFileWithUUID } from '~/redux/types';
|
||||
import uuid from 'uuid4';
|
||||
import { UPLOAD_SUBJECTS, UPLOAD_TARGETS, UPLOAD_TYPES } from '~/redux/uploads/constants';
|
||||
import { path } from 'ramda';
|
||||
import * as UPLOAD_ACTIONS from "~/redux/uploads/actions";
|
||||
import * as AUTH_ACTIONS from "~/redux/auth/actions";
|
||||
import { Icon } from "~/components/input/Icon";
|
||||
import * as UPLOAD_ACTIONS from '~/redux/uploads/actions';
|
||||
import * as AUTH_ACTIONS from '~/redux/auth/actions';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
user: pick(["id"], selectAuthUser(state)),
|
||||
profile: pick(["is_loading", "user"], selectAuthProfile(state)),
|
||||
uploads: pick(["statuses", "files"], selectUploads(state))
|
||||
user: pick(['id'], selectAuthUser(state)),
|
||||
profile: pick(['is_loading', 'user'], selectAuthProfile(state)),
|
||||
uploads: pick(['statuses', 'files'], selectUploads(state)),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
uploadUploadFiles: UPLOAD_ACTIONS.uploadUploadFiles,
|
||||
authPatchUser: AUTH_ACTIONS.authPatchUser
|
||||
authPatchUser: AUTH_ACTIONS.authPatchUser,
|
||||
};
|
||||
|
||||
type IProps = ReturnType<typeof mapStateToProps> &
|
||||
typeof mapDispatchToProps & {};
|
||||
type IProps = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
|
||||
|
||||
const ProfileAvatarUnconnected: FC<IProps> = ({
|
||||
user: { id },
|
||||
profile: { is_loading, user },
|
||||
uploads: { statuses, files },
|
||||
uploadUploadFiles,
|
||||
authPatchUser
|
||||
authPatchUser,
|
||||
}) => {
|
||||
const can_edit = !is_loading && id && id === user.id;
|
||||
const can_edit = !is_loading && id && id === user?.id;
|
||||
|
||||
const [temp, setTemp] = useState<string>(null);
|
||||
const [temp, setTemp] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!can_edit) return;
|
||||
|
@ -45,7 +44,7 @@ const ProfileAvatarUnconnected: FC<IProps> = ({
|
|||
Object.entries(statuses).forEach(([id, status]) => {
|
||||
if (temp === id && !!status.uuid && files[status.uuid]) {
|
||||
authPatchUser({ photo: files[status.uuid] });
|
||||
setTemp(null);
|
||||
setTemp('');
|
||||
}
|
||||
});
|
||||
}, [statuses, files, temp, can_edit, authPatchUser]);
|
||||
|
@ -58,11 +57,11 @@ const ProfileAvatarUnconnected: FC<IProps> = ({
|
|||
temp_id: uuid(),
|
||||
subject: UPLOAD_SUBJECTS.AVATAR,
|
||||
target: UPLOAD_TARGETS.PROFILES,
|
||||
type: UPLOAD_TYPES.IMAGE
|
||||
type: UPLOAD_TYPES.IMAGE,
|
||||
})
|
||||
);
|
||||
|
||||
setTemp(path([0, "temp_id"], items));
|
||||
setTemp(path([0, 'temp_id'], items) || '');
|
||||
uploadUploadFiles(items.slice(0, 1));
|
||||
},
|
||||
[uploadUploadFiles, setTemp]
|
||||
|
@ -81,13 +80,15 @@ const ProfileAvatarUnconnected: FC<IProps> = ({
|
|||
[onUpload, can_edit]
|
||||
);
|
||||
|
||||
const backgroundImage = is_loading
|
||||
? undefined
|
||||
: `url("${user && getURL(user.photo, PRESETS.avatar)}")`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.avatar}
|
||||
style={{
|
||||
backgroundImage: is_loading
|
||||
? null
|
||||
: `url("${user && getURL(user.photo, PRESETS.avatar)}")`
|
||||
backgroundImage,
|
||||
}}
|
||||
>
|
||||
{can_edit && <input type="file" onInput={onInputChange} />}
|
||||
|
@ -100,9 +101,6 @@ const ProfileAvatarUnconnected: FC<IProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const ProfileAvatar = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProfileAvatarUnconnected);
|
||||
const ProfileAvatar = connect(mapStateToProps, mapDispatchToProps)(ProfileAvatarUnconnected);
|
||||
|
||||
export { ProfileAvatar };
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React, { FC, ReactNode } from 'react';
|
||||
import { IUser } from '~/redux/auth/types';
|
||||
import { IAuthState, IUser } from '~/redux/auth/types';
|
||||
import styles from './styles.module.scss';
|
||||
import { Group } from '~/components/containers/Group';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
|
@ -14,7 +14,7 @@ interface IProps {
|
|||
is_loading?: boolean;
|
||||
is_own?: boolean;
|
||||
|
||||
setTab?: (tab: string) => void;
|
||||
setTab?: (tab: IAuthState['profile']['tab']) => void;
|
||||
|
||||
content?: ReactNode;
|
||||
}
|
||||
|
@ -26,16 +26,16 @@ const ProfileInfo: FC<IProps> = ({ user, tab, is_loading, is_own, setTab, conten
|
|||
|
||||
<div className={styles.field}>
|
||||
<div className={styles.name}>
|
||||
{is_loading ? <Placeholder width="80%" /> : user.fullname || user.username}
|
||||
{is_loading ? <Placeholder width="80%" /> : user?.fullname || user?.username}
|
||||
</div>
|
||||
|
||||
<div className={styles.description}>
|
||||
{is_loading ? <Placeholder /> : getPrettyDate(user.last_seen)}
|
||||
{is_loading ? <Placeholder /> : getPrettyDate(user?.last_seen)}
|
||||
</div>
|
||||
</div>
|
||||
</Group>
|
||||
|
||||
<ProfileTabs tab={tab} is_own={is_own} setTab={setTab} />
|
||||
<ProfileTabs tab={tab} is_own={!!is_own} setTab={setTab} />
|
||||
|
||||
{content}
|
||||
</div>
|
||||
|
|
|
@ -20,10 +20,10 @@ const ProfileLayoutUnconnected: FC<IProps> = ({ history, nodeSetCoverImage }) =>
|
|||
const {
|
||||
params: { username },
|
||||
} = useRouteMatch<{ username: string }>();
|
||||
const [user, setUser] = useState<IUser>(null);
|
||||
const [user, setUser] = useState<IUser | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (user) setUser(null);
|
||||
if (user) setUser(undefined);
|
||||
}, [username]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -31,7 +31,7 @@ const ProfileMessagesUnconnected: FC<IProps> = ({
|
|||
messagesRefreshMessages,
|
||||
}) => {
|
||||
const wasAtBottom = useRef(true);
|
||||
const [wrap, setWrap] = useState<HTMLDivElement>(null);
|
||||
const [wrap, setWrap] = useState<HTMLDivElement | undefined>(undefined);
|
||||
const [editingMessageId, setEditingMessageId] = useState(0);
|
||||
|
||||
const onEditMessage = useCallback((id: number) => setEditingMessageId(id), [setEditingMessageId]);
|
||||
|
@ -95,31 +95,33 @@ const ProfileMessagesUnconnected: FC<IProps> = ({
|
|||
if (!messages.messages.length || profile.is_loading)
|
||||
return <NodeNoComments is_loading={messages.is_loading_messages || profile.is_loading} />;
|
||||
|
||||
return (
|
||||
messages.messages.length > 0 && (
|
||||
<div className={styles.messages} ref={storeRef}>
|
||||
{messages.messages
|
||||
.filter(message => !!message.text)
|
||||
.map((
|
||||
message // TODO: show files / memo
|
||||
) => (
|
||||
<Message
|
||||
message={message}
|
||||
incoming={id !== message.from.id}
|
||||
key={message.id}
|
||||
onEdit={onEditMessage}
|
||||
onDelete={onDeleteMessage}
|
||||
isEditing={editingMessageId === message.id}
|
||||
onCancelEdit={onCancelEdit}
|
||||
onRestore={onRestoreMessage}
|
||||
/>
|
||||
))}
|
||||
if (messages.messages.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
{!messages.is_loading_messages && messages.messages.length > 0 && (
|
||||
<div className={styles.placeholder}>Когда-нибудь здесь будут еще сообщения</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div className={styles.messages} ref={storeRef}>
|
||||
{messages.messages
|
||||
.filter(message => !!message.text)
|
||||
.map((
|
||||
message // TODO: show files / memo
|
||||
) => (
|
||||
<Message
|
||||
message={message}
|
||||
incoming={id !== message.from.id}
|
||||
key={message.id}
|
||||
onEdit={onEditMessage}
|
||||
onDelete={onDeleteMessage}
|
||||
isEditing={editingMessageId === message.id}
|
||||
onCancelEdit={onCancelEdit}
|
||||
onRestore={onRestoreMessage}
|
||||
/>
|
||||
))}
|
||||
|
||||
{!messages.is_loading_messages && messages.messages.length > 0 && (
|
||||
<div className={styles.placeholder}>Когда-нибудь здесь будут еще сообщения</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import React, { FC, useMemo } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import { IAuthState } from '~/redux/auth/types';
|
||||
import { getURL } from '~/utils/dom';
|
||||
import { formatText, getURL } from '~/utils/dom';
|
||||
import { PRESETS, URLS } from '~/constants/urls';
|
||||
import { Placeholder } from '~/components/placeholders/Placeholder';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Icon } from '~/components/input/Icon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './styles.module.scss';
|
||||
import markdown from '~/styles/common/markdown.module.scss';
|
||||
|
||||
interface IProps {
|
||||
profile: IAuthState['profile'];
|
||||
|
@ -26,11 +29,11 @@ const ProfilePageLeft: FC<IProps> = ({ username, profile }) => {
|
|||
<div className={styles.region_wrap}>
|
||||
<div className={styles.region}>
|
||||
<div className={styles.name}>
|
||||
{profile.is_loading ? <Placeholder /> : profile.user.fullname}
|
||||
{profile.is_loading ? <Placeholder /> : profile?.user?.fullname}
|
||||
</div>
|
||||
|
||||
<div className={styles.username}>
|
||||
{profile.is_loading ? <Placeholder /> : `~${profile.user.username}`}
|
||||
{profile.is_loading ? <Placeholder /> : `~${profile?.user?.username}`}
|
||||
</div>
|
||||
|
||||
<div className={styles.menu}>
|
||||
|
@ -53,7 +56,9 @@ const ProfilePageLeft: FC<IProps> = ({ username, profile }) => {
|
|||
</div>
|
||||
|
||||
{profile && profile.user && profile.user.description && false && (
|
||||
<div className={styles.description}>{profile.user.description}</div>
|
||||
<div className={classNames(styles.description, markdown.wrapper)}>
|
||||
{formatText(profile?.user?.description || '')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,38 +1,49 @@
|
|||
import React, { FC } from 'react';
|
||||
import React, { FC, useCallback } from 'react';
|
||||
import styles from './styles.module.scss';
|
||||
import classNames from 'classnames';
|
||||
import { IAuthState } from '~/redux/auth/types';
|
||||
|
||||
interface IProps {
|
||||
tab: string;
|
||||
is_own: boolean;
|
||||
setTab: (tab: string) => void;
|
||||
setTab?: (tab: IAuthState['profile']['tab']) => void;
|
||||
}
|
||||
|
||||
const ProfileTabs: FC<IProps> = ({ tab, is_own, setTab }) => (
|
||||
<div className={styles.wrap}>
|
||||
<div
|
||||
className={classNames(styles.tab, { [styles.active]: tab === 'profile' })}
|
||||
onClick={() => setTab('profile')}
|
||||
>
|
||||
Профиль
|
||||
const ProfileTabs: FC<IProps> = ({ tab, is_own, setTab }) => {
|
||||
const changeTab = useCallback(
|
||||
(tab: IAuthState['profile']['tab']) => () => {
|
||||
if (!setTab) return;
|
||||
setTab(tab);
|
||||
},
|
||||
[setTab]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div
|
||||
className={classNames(styles.tab, { [styles.active]: tab === 'profile' })}
|
||||
onClick={changeTab('profile')}
|
||||
>
|
||||
Профиль
|
||||
</div>
|
||||
<div
|
||||
className={classNames(styles.tab, { [styles.active]: tab === 'messages' })}
|
||||
onClick={changeTab('messages')}
|
||||
>
|
||||
Сообщения
|
||||
</div>
|
||||
{is_own && (
|
||||
<>
|
||||
<div
|
||||
className={classNames(styles.tab, { [styles.active]: tab === 'settings' })}
|
||||
onClick={changeTab('settings')}
|
||||
>
|
||||
Настройки
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={classNames(styles.tab, { [styles.active]: tab === 'messages' })}
|
||||
onClick={() => setTab('messages')}
|
||||
>
|
||||
Сообщения
|
||||
</div>
|
||||
{is_own && (
|
||||
<>
|
||||
<div
|
||||
className={classNames(styles.tab, { [styles.active]: tab === 'settings' })}
|
||||
onClick={() => setTab('settings')}
|
||||
>
|
||||
Настройки
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export { ProfileTabs };
|
||||
|
|
|
@ -56,7 +56,7 @@ const ProfileSidebarUnconnected: FC<Props> = ({
|
|||
</Switch>
|
||||
|
||||
<div className={classNames(styles.wrap, styles.secondary)}>
|
||||
<ProfileSidebarInfo is_loading={is_loading} user={user} />
|
||||
{!!user && <ProfileSidebarInfo is_loading={is_loading} user={user} />}
|
||||
<ProfileSidebarMenu path={url} />
|
||||
<Filler />
|
||||
</div>
|
||||
|
|
|
@ -35,7 +35,10 @@ const TagSidebarUnconnected: FC<Props> = ({ nodes, tagLoadNodes, tagSetNodes })
|
|||
|
||||
useEffect(() => {
|
||||
tagLoadNodes(tag);
|
||||
return () => tagSetNodes({ list: [], count: 0 });
|
||||
|
||||
return () => {
|
||||
tagSetNodes({ list: [], count: 0 });
|
||||
};
|
||||
}, [tag]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue