Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lecture 5 #412

Open
wants to merge 18 commits into
base: lecture-5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
89f8286
feat: создан redux для запроса комментариев, созданы компоненты Comme…
MelnikovaEA Oct 8, 2024
49d62e6
feat: комментарии рендерятся согласно иерархии
MelnikovaEA Oct 9, 2024
932990f
feat: добавлена утилита для форматирования даты
MelnikovaEA Oct 9, 2024
5d89328
feat: рализовано окно ответа на комментарии
MelnikovaEA Oct 9, 2024
1a026ad
feat: рализована отправка ответных комментариев
MelnikovaEA Oct 9, 2024
1cc3996
feat: добавлен компонент Textarea
MelnikovaEA Oct 10, 2024
7f5f8e5
feat: добавлена функция поиска последнего комментария в ветке
MelnikovaEA Oct 10, 2024
0d73ba8
feat: выполнено второе задание
MelnikovaEA Oct 10, 2024
2839c88
fix: удален ненужый лог и ненужный пропс
MelnikovaEA Oct 10, 2024
831e5e2
fix: добавлены плейсхолдеры для textarea, исправлены стили и функция …
MelnikovaEA Oct 13, 2024
eb48843
fix: добавлен disabled для кнопок отправки в формах добавленя коммент…
MelnikovaEA Oct 13, 2024
66a71aa
fix: объединены обратно comment и comments в redux. Страница больше н…
MelnikovaEA Oct 13, 2024
af8c3b6
fix: к форме ответа добавлен автоскролл
MelnikovaEA Oct 13, 2024
0ea3412
fix: плейсхолдер в форме ответа теперь отображает корректное имя авто…
MelnikovaEA Oct 13, 2024
7767e32
fix: изменена функция list-to-tree, исправлена ошибка из консоли об у…
MelnikovaEA Oct 13, 2024
91299dc
fix: имя авторизованного пользователя в комментариях теперь серое
MelnikovaEA Oct 13, 2024
8db1d08
feat: добавлен readme
MelnikovaEA Oct 15, 2024
ba00f56
feat: при смене языка приложения страница больше не обновляется, текс…
MelnikovaEA Oct 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Pull requests

Lecture-1: https://github.com/ylabio/react-webinar-3/pull/26

Lecture-2: второй пуллреквест потерялся, но вот ссылка на ветку в моем репозитории:
https://github.com/MelnikovaEA/react-webinar-3/tree/lecture-2

Lecture-3: https://github.com/ylabio/react-webinar-3/pull/293

Lecture-4: https://github.com/ylabio/react-webinar-3/pull/351

Lecture-5: https://github.com/ylabio/react-webinar-3/pull/412
1 change: 1 addition & 0 deletions src/api/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

class APIService {
/**
* @param services {Services} Менеджер сервисов
Expand Down
70 changes: 61 additions & 9 deletions src/app/article/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import Spinner from '../../components/spinner';
import ArticleCard from '../../components/article-card';
import LocaleSelect from '../../containers/locale-select';
import TopHead from '../../containers/top-head';
import { useDispatch, useSelector } from 'react-redux';
import { useDispatch, useSelector as useReduxSelector } from 'react-redux';
import useSelector from '../../hooks/use-selector';
import shallowequal from 'shallowequal';
import articleActions from '../../store-redux/article/actions';
import commentsActions from '../../store-redux/comments/actions';
import treeToList from '../../utils/tree-to-list';
import listToTree from '../../utils/list-to-tree';

function Article() {
const store = useStore();
Expand All @@ -21,21 +25,61 @@ function Article() {
// Параметры из пути /articles/:id

const params = useParams();
const { t, language} = useTranslate();

useInit(() => {
//store.actions.article.load(params.id);
dispatch(articleActions.load(params.id));
}, [params.id]);
useInit(
async () => {
await Promise.all([
dispatch(articleActions.load(params.id)),
dispatch(commentsActions.load(params.id)),
]);
},
[params.id, language],
true,
);

const select = useSelector(
const select = useReduxSelector(
state => ({
article: state.article.data,
waiting: state.article.waiting,
comments: state.comments.data,
waiting: state.article.waiting && state.comments.waiting,
}),
shallowequal,
); // Нужно указать функцию для сравнения свойства объекта, так как хуком вернули объект

const { t } = useTranslate();
const selectStore = useSelector(state => ({
exists: state.session.exists,
currentUserName: state.session.user?.profile?.name,
}));

// Формируем массив комментариев для рендера
const transformedComments = useMemo(() => {
// если загрузились комментарии из апи
if (select.comments && select.comments?.items?.length > 0) {
// сортируем их по родителям
const tree = listToTree(select.comments.items);

// формируем массив для рендера комментариев согласно их иерархии
const items = treeToList(tree, (item, level) => {
return {
_id: item._id,
text: item.text,
dateCreate: item.dateCreate,
type: item?.parent?._type,
level: level,
author: item?.author?.profile?.name || 'Unknown',
isDeleted: item.isDeleted,
};
});

return {
...select.comments,
items: items,
};
}

return select.comments;
}, [select.comments]);

const callbacks = {
// Добавление в корзину
Expand All @@ -50,7 +94,15 @@ function Article() {
</Head>
<Navigation />
<Spinner active={select.waiting}>
<ArticleCard article={select.article} onAdd={callbacks.addToBasket} t={t} />
<ArticleCard
article={select.article}
onAdd={callbacks.addToBasket}
replyComment={callbacks.replyComment}
t={t}
comments={transformedComments}
session={selectStore.exists}
currentUserName={selectStore.currentUserName}
/>
</Spinner>
</PageLayout>
);
Expand Down
5 changes: 2 additions & 3 deletions src/app/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ import TopHead from '../../containers/top-head';

function Main() {
const store = useStore();
const { t, language} = useTranslate();

useInit(
async () => {
await Promise.all([store.actions.catalog.initParams(), store.actions.categories.load()]);
},
[],
[language],
true,
);

const { t } = useTranslate();

return (
<PageLayout>
<TopHead />
Expand Down
9 changes: 3 additions & 6 deletions src/app/profile/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { memo, useCallback, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { memo } from 'react';
import useStore from '../../hooks/use-store';
import useSelector from '../../hooks/use-selector';
import useTranslate from '../../hooks/use-translate';
Expand All @@ -8,25 +7,23 @@ import PageLayout from '../../components/page-layout';
import Head from '../../components/head';
import Navigation from '../../containers/navigation';
import Spinner from '../../components/spinner';
import ArticleCard from '../../components/article-card';
import LocaleSelect from '../../containers/locale-select';
import TopHead from '../../containers/top-head';
import ProfileCard from '../../components/profile-card';

function Profile() {
const store = useStore();
const { t, language } = useTranslate();

useInit(() => {
store.actions.profile.load();
}, []);
}, [language]);

const select = useSelector(state => ({
profile: state.profile.data,
waiting: state.profile.waiting,
}));

const { t } = useTranslate();

return (
<PageLayout>
<TopHead />
Expand Down
17 changes: 16 additions & 1 deletion src/components/article-card/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@ import PropTypes from 'prop-types';
import { cn as bem } from '@bem-react/classname';
import numberFormat from '../../utils/number-format';
import './style.css';
import Comments from '../../components/comments';

function ArticleCard(props) {
const { article, onAdd = () => {}, t = text => text } = props;
const {
article,
onAdd = () => {},
t = text => text,
comments,
session,
currentUserName
} = props;

const cn = bem('ArticleCard');
return (
<div className={cn()}>
Expand All @@ -29,6 +38,11 @@ function ArticleCard(props) {
<div className={cn('value')}>{numberFormat(article.price)} ₽</div>
</div>
<button onClick={() => onAdd(article._id)}>{t('article.add')}</button>
<Comments
comments={comments}
session={session}
currentUserName={currentUserName}
/>
</div>
);
}
Expand All @@ -44,6 +58,7 @@ ArticleCard.propTypes = {
}).isRequired,
onAdd: PropTypes.func,
t: PropTypes.func,
comments: PropTypes.object,
};

export default memo(ArticleCard);
64 changes: 64 additions & 0 deletions src/components/comment/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {memo, useRef, useEffect} from 'react';
import { cn as bem } from '@bem-react/classname';
import useTranslate from '../../hooks/use-translate';
import dateFormat from '../../utils/date-format';
import './style.css';
import { Link, useLocation } from 'react-router-dom';
import ReplyWindow from '../reply-window';

function Comment({ id, name, dateCreate, text, session, isVisible, onToggleReply, onSubmit, userId, currentCommentUsername, currentUserName }) {
const cn = bem('Comment');
const { t } = useTranslate();
const location = useLocation();
const replyWindowRef = useRef(null);

useEffect(() => {
if (isVisible && replyWindowRef.current) {
replyWindowRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, [isVisible]);

return (
<div className={cn()}>
<div>
<span className={`${cn('author')} ${name === currentUserName ? cn('highlighted') : ''}`}>{name}</span>
<span className={cn('dateCreate')}>
{dateFormat(dateCreate).day} {t(dateFormat(dateCreate).month)}{' '}
{dateFormat(dateCreate).year} {t('prepositions.atTime')} {dateFormat(dateCreate).time}
</span>
</div>
<article className={cn('text')}>{text}</article>
<div className={cn('action')} onClick={() => onToggleReply(id)}>
{t('comments.reply')}
</div>
{isVisible && (
<div ref={replyWindowRef} className={cn('replyWindow')}>
{session ? (
<div className={cn('reply')}>
<ReplyWindow
id={id}
onToggleReply={onToggleReply}
theme="medium"
placeholder={`Мой ответ для ${currentCommentUsername}`}
onSubmitReply={onSubmit}
userId={userId}
/>
</div>
) : (
<div className={cn('reply')}>
<p className={cn('link')}>
<Link to={'/login'} state={{ back: location.pathname }}>
{t('comments.enter')}
</Link>
, {t('comments.toBeAbleAnswer')}.{' '}
<span onClick={onToggleReply}>{t('comments.cancel')}</span>
</p>
</div>
)}
</div>
)}
</div>
);
}

export default memo(Comment);
62 changes: 62 additions & 0 deletions src/components/comment/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
.Comment {
display: flex;
flex-direction: column;
align-items: flex-start;
font-size: 14px;
padding-bottom: 30px;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
}

.Comment-author {
font-weight: bold;
padding-right: 10px;
}

.Comment-highlighted {
color: #666666;
}

.Comment-dateCreate {
color: #666666;
}

.Comment-text {
padding: 10px 0;
}

.Comment-action {
color: #0087E9;
cursor: pointer;
}

.Comment-replyWindow {
width: 100%;
}

.Comment-reply {
padding-top: 30px;
width: 100%;
box-sizing: border-box;
}

.Comment-link {
margin: 0;
padding: 0;
font-size: 16px;
}

.Comment-link a {
margin: 0;
padding: 0;
color: #0087E9;
}

.Comment-link span {
color: #666666;
text-decoration: underline;
cursor: pointer;
}


Loading
Loading