Skip to content

Commit

Permalink
Added comment form in article page; profiles pages
Browse files Browse the repository at this point in the history
  • Loading branch information
yaazzik committed Jul 6, 2023
1 parent 9c4d626 commit fdda5bb
Show file tree
Hide file tree
Showing 32 changed files with 363 additions and 81 deletions.
106 changes: 91 additions & 15 deletions json-server/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
"img": "https://teknotower.com/wp-content/uploads/2020/11/js.png",
"views": 1022,
"createdAt": "26.02.2022",
"type": ["IT"],
"type": [
"IT"
],
"blocks": [
{
"id": "1",
Expand Down Expand Up @@ -105,30 +107,104 @@
"text": "Nice!",
"articleId": "1",
"userId": "1"
},
{
"articleId": "1",
"userId": 1,
"text": "123",
"id": "QmJ2O6c"
},
{
"articleId": "1",
"userId": 1,
"text": "123",
"id": "VK5kSxS"
},
{
"articleId": "1",
"userId": 1,
"text": "123",
"id": "uIdCILo"
},
{
"articleId": "1",
"userId": 1,
"text": "123",
"id": "yVoqeh7"
},
{
"articleId": "1",
"userId": 1,
"text": "123",
"id": "l0qyiBv"
},
{
"articleId": "1",
"userId": 1,
"text": "Good!",
"id": "a9mKkdo"
},
{
"articleId": "1",
"userId": 1,
"text": "Hello",
"id": "YS-aKA4"
},
{
"articleId": "1",
"userId": "1",
"text": "Hola",
"id": "opUOR2P"
},
{
"articleId": "1",
"userId": "1",
"text": "123",
"id": "SCBQgF6"
},
{
"articleId": "1",
"userId": "1",
"text": "321",
"id": "NOPTofp"
}
],
"users": [
{
"id": 1,
"id": "1",
"username": "admin",
"password": "123",
"avatar": "https://www.computerhope.com/jargon/g/guest-user.png"
},
{
"id": 2,
"id": "2",
"username": "nico",
"password": "123",
"avatar": "https://www.computerhope.com/jargon/g/guest-user.png"
"avatar": "https://wallpaperaccess.com/full/4595689.jpg"
}
],
"profile": {
"firstname": "Russkiy",
"lastname": "Luka",
"age": 22,
"currency": "EUR",
"country": "CL",
"city": "Saint-Petersburg",
"username": "Karotish",
"avatar": "https://www.computerhope.com/jargon/g/guest-user.png"
}
}
"profile": [
{
"id": "1",
"firstname": "Rus",
"lastname": "Luka",
"age": 22,
"currency": "EUR",
"country": "CL",
"city": "Saint-Petersburg",
"username": "Karotish",
"avatar": "https://www.computerhope.com/jargon/g/guest-user.png"
},
{
"id": "2",
"username": "Hassan",
"avatar": "https://wallpaperaccess.com/full/4595689.jpg",
"city": "Boston",
"currency": "USD",
"country": "USA",
"age": 23,
"firstname": "Ellie",
"lastname": "Heisenberg"
}
]
}
4 changes: 3 additions & 1 deletion public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@
"Авторизация": "Authorization",
"Выйти": "Logout",
"Ошибка": "Error",
"Статьи": "Articles"
"Статьи": "Articles",
"Ваш комментарий": "Your comment",
"Отправить": "Send"
}
5 changes: 3 additions & 2 deletions public/locales/ru/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"Авторизация": "Авторизация",
"Выйти": "Выйти",
"Ошибка": "Ошибка",
"Статьи": "Статьи"

"Статьи": "Статьи",
"Ваш комментарий": "Ваш комментарий",
"Отправить": "Отправить"
}

2 changes: 2 additions & 0 deletions src/app/providers/StoreProvider/config/StateSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { NavigateOptions } from 'react-router';
import { To } from 'history';
import { ArticleDetailsSchema } from 'entities/Article';
import { ArticleDetailsCommentsSchema } from 'pages/ArticleDetailsPage';
import { AddCommentFormSchema } from 'features/AddCommentForm';

export interface StateSchema {
counter: CounterSchema;
Expand All @@ -20,6 +21,7 @@ export interface StateSchema {
profile?: ProfileSchema;
articleDetails?: ArticleDetailsSchema;
articleDetailsComments?: ArticleDetailsCommentsSchema;
addCommentForm?: AddCommentFormSchema;
}

export type StateSchemaKeys = keyof StateSchema;
Expand Down
1 change: 1 addition & 0 deletions src/entities/Article/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export { ArticleDetailsSchema } from './model/types/articleDetailsSchema';
export { ArticleImageBlockComponent } from './ui/ArticleImageBlockComponent/ArticleImageBlockComponent';
export { ArticleTextBlockComponent } from './ui/ArticleTextBlockComponent/ArticleTextBlockComponent';
export { ArticleCodeBlockComponent } from './ui/ArticleCodeBlockComponent/ArticleCodeBlockComponent';
export { getArticleDetailsData } from './model/selectors/articleDetailsSelectors';
21 changes: 13 additions & 8 deletions src/entities/Comment/ui/CommentItem/CommentItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { memo } from 'react';
import { Avatar } from 'shared/ui/Avatar/Avatar';
import { Text } from 'shared/ui/Text/ui/Text';
import { Skeleton } from 'shared/ui/Skeleton/Skeleton';
import { AppLink } from 'shared/ui/AppLink/AppLink';
import { RoutePath } from 'shared/config/routeConfig/routeConfig';
import cls from './CommentItem.module.scss';
import { Comment } from '../../model/types/comment';

Expand Down Expand Up @@ -33,14 +35,17 @@ export const CommentItem = memo((props: CommentItemProps) => {

return (
<div className={classNames(cls.CommentItem, {}, [className])}>
<div className={cls.header}>
{
comment.user.avatar
? <Avatar src={comment.user.avatar} className={cls.avatar} size="40px" />
: <Avatar className={cls.avatar} size="40px" />
}
<Text title={comment.user.username} />
</div>
<AppLink to={`${RoutePath.profile}${comment.user.id}`}>
<div className={cls.header}>
{
comment.user.avatar
? <Avatar src={comment.user.avatar} className={cls.avatar} size="40px" />
: <Avatar className={cls.avatar} size="40px" />
}
<Text title={comment.user.username} />
</div>
</AppLink>

<Text text={comment.text} />
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/entities/Comment/ui/CommentList/CommentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const CommentList = memo((props: CommentListProps) => {
<div className={classNames(cls.CommentList, {}, [className])}>
{
comments?.length
? comments.map((comment) => (
? comments.reverse().map((comment) => (
<CommentItem
className={cls.comment}
isLoading={isLoading}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('fetchProfileData.test', () => {
test('success login', async () => {
const thunk = new TestAsyncThunk(fetchProfileData);
thunk.api.get.mockReturnValue(Promise.resolve({ data }));
const result = await thunk.callThunk();
const result = await thunk.callThunk('1');

expect(thunk.api.get).toHaveBeenCalled();
expect(result.meta.requestStatus).toEqual('fulfilled');
Expand All @@ -41,7 +41,7 @@ describe('fetchProfileData.test', () => {
test('rejected login', async () => {
const thunk = new TestAsyncThunk(fetchProfileData);
thunk.api.get.mockReturnValue(Promise.resolve({ status: 403 }));
const result = await thunk.callThunk();
const result = await thunk.callThunk('1');

expect(result.meta.requestStatus).toEqual('rejected');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
import { ThunkConfig } from 'app/providers/StoreProvider';
import { ProfileType } from '../../types/profile';

export const fetchProfileData = createAsyncThunk<ProfileType, void, ThunkConfig<string>>(
export const fetchProfileData = createAsyncThunk<ProfileType, string, ThunkConfig<string>>(
'profile/fetch ProfileData',
async (_, thunkAPI) => {
async (id, thunkAPI) => {
const { extra, rejectWithValue } = thunkAPI;
try {
const response = await extra.api.get<ProfileType>('/profile');
const response = await extra.api.get<ProfileType>(`/profile/${id}`);

if (!response.data) {
throw new Error();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const data = {
age: 23,
firstname: 'Ellie',
lastname: 'Heisenberg',
id: '1',
};

describe('updateProfileData.test', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const updateProfileData = createAsyncThunk<
}

try {
const response = await extra.api.put<ProfileType>('/profile', formData);
const response = await extra.api.put<ProfileType>(`/profile/${formData?.id}`, formData);

if (!response.data) {
throw new Error();
Expand Down
1 change: 1 addition & 0 deletions src/entities/Profile/model/types/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface ProfileType {
city?: string;
username?: string;
avatar?: string;
id?: string
}

export interface ProfileSchema {
Expand Down
2 changes: 2 additions & 0 deletions src/features/AddCommentForm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AddCommentFormSchema } from './model/types/addCommentForm';
export { AddCommentFormAsync as AddCommentForm } from './ui/AddCommentForm/AddCommentForm.async';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { StateSchema } from 'app/providers/StoreProvider';

export const getAddCommentFormText = (state: StateSchema) => state.addCommentForm?.text;
export const getAddCommentFormError = (state: StateSchema) => state.addCommentForm?.error;
31 changes: 31 additions & 0 deletions src/features/AddCommentForm/model/slice/addCommentFormSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { AddCommentFormSchema } from '../types/addCommentForm';

const initialState: AddCommentFormSchema = {
error: '',
text: '',
};

export const addCommentFormSlice = createSlice({
name: 'addCommentForm',
initialState,
reducers: {
setText: (state, action: PayloadAction<string>) => {
state.text = action.payload;
},
},
extraReducers: (builder) => {
// builder.addCase(_.pending, (state) => {
// state.error = undefined;
// })
// .addCase(_.fulfilled, (state) => {
//
// })
// .addCase(_.rejected, (state, action) => {
// state.error = action.payload;
// });
},
});

export const { actions: addCommentFormActions } = addCommentFormSlice;
export const { reducer: addCommentFormReducer } = addCommentFormSlice;
4 changes: 4 additions & 0 deletions src/features/AddCommentForm/model/types/addCommentForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface AddCommentFormSchema {
text?: string;
error?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { FC, lazy } from 'react';
import { AddCommentFormProps } from './AddCommentForm';

// eslint-disable-next-line no-return-await
export const AddCommentFormAsync = lazy<FC<AddCommentFormProps>>(async () => await new Promise((resolve) => {
setTimeout(() => { resolve(import('./AddCommentForm')); }, 1500);
}));
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.AddCommentForm {
border: 1px solid var(--primary-color);
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
53 changes: 53 additions & 0 deletions src/features/AddCommentForm/ui/AddCommentForm/AddCommentForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { classNames } from 'shared/lib/classNames/classNames';
import { memo, useCallback } from 'react';
import { Input } from 'shared/ui/Input/Input';
import { Button, ButtonTheme } from 'shared/ui/Button/Button';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { useAppDispatch } from 'shared/lib/hooks/useAppDispatch/useAppDispatch';
import { DynamicModuleLoader, ReducersList } from 'shared/lib/components/DynamicModuleLoader/DynamicModuleLoader';
import { addCommentFormActions, addCommentFormReducer } from '../../model/slice/addCommentFormSlice';
import {
getAddCommentFormError,
getAddCommentFormText,
} from '../../model/selectors/addCommentFormSelectors';
import cls from './AddCommentForm.module.scss';

const reducers: ReducersList = {
addCommentForm: addCommentFormReducer,
};
export interface AddCommentFormProps {
className?: string;
onSendComment: (text: string) => void
}

const AddCommentForm = memo((props: AddCommentFormProps) => {
const {
className,
onSendComment,
} = props;

const { t } = useTranslation();
const text = useSelector(getAddCommentFormText);
const dispatch = useAppDispatch();

const onCommentTextChange = useCallback((text: string) => {
dispatch(addCommentFormActions.setText(text));
}, [dispatch]);

const onSendHandler = useCallback(() => {
onSendComment(text || '');
onCommentTextChange('');
}, [onCommentTextChange, onSendComment, text]);

return (
<DynamicModuleLoader reducers={reducers}>
<div className={classNames(cls.AddCommentForm, {}, [className])}>
<Input value={text} onChange={onCommentTextChange} placeholder={t('Ваш комментарий')} />
<Button onClick={onSendHandler} className={className} theme={ButtonTheme.SUBMIT}>{t('Отправить')}</Button>
</div>
</DynamicModuleLoader>
);
});

export default AddCommentForm;
1 change: 0 additions & 1 deletion src/features/AuthByUsername/model/slice/loginSlice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { actions } from '@storybook/addon-actions';
import { loginByUsername } from 'features/AuthByUsername/model/services/loginByUsername/loginByUsername';
import { LoginSchema } from '../types/loginSchema';

Expand Down
Loading

1 comment on commit fdda5bb

@vercel
Copy link

@vercel vercel bot commented on fdda5bb Jul 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.