Skip to content

Commit

Permalink
added feature AddArticleComents and entiti Comment, added profile pag…
Browse files Browse the repository at this point in the history
…es for users, added article list, article item and skeleton
  • Loading branch information
Onvix24 committed Feb 18, 2024
1 parent b2cf4cb commit 2cde7a2
Show file tree
Hide file tree
Showing 80 changed files with 1,732 additions and 174 deletions.
9 changes: 8 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,14 @@ module.exports = {
"react-hooks/exhaustive-deps": "error", // Checks effect dependencies
"no-param-reassign": "off",
"react/display-name": "off",
"no-undef": "off"
"no-undef": "off",
"react/self-closing-comp": [
"error",
{
"component": true,
"html": false
}
]
},
globals: {
__IS_DEV__: true,
Expand Down
10 changes: 4 additions & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,22 @@
"export const { reducer: <FTName | lowercase>Reducer } = <FTName | lowercase>Slice;"
],
"component": [
"import { useTranslation } from \"react-i18next\";",
"import cls from \"./<FTName>.module.scss\";",
"import { classNames } from \"shared/lib/classNames/classNames\";",
"import { memo } from \"react\";",
"",
"interface <FTName>Props {",
"\tclassName?: string",
"}",
"",
"export const <FTName> = ({ className } : <FTName>Props) => {",
"\t",
"\tconst { t } = useTranslation();",
"export const <FTName> = memo(({ className } : <FTName>Props) => {",
"\t",
"\treturn (",
"\t\t<div className={classNames(cls.<FTName>, {}, [className])}>",
"\t\t\t$1",
"\t\t\t<FTName>",
"\t\t</div>",
"\t);",
"};"
"});"
],
"exports": ["export { <FTName> } from \"./ui/<FTName>\";"],
"selectorTest": [
Expand Down
137 changes: 122 additions & 15 deletions json-server/db.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,137 @@
"userId": "2"
}
],
"articles": [
{
"id": "1",
"userId": "1",
"title": "JS news 1",
"subtitle": "Що нового в JS за 2023 рік?",
"img": "https://resize.indiatvnews.com/en/centered/newbucket/1200_675/2023/06/nature-1686808887.jpg",
"views": 2424,
"createdAt": "24.02.2023",
"type": [
"IT"
],
"blocks": [
{
"id": "1",
"type": "TEXT",
"title": "Заголовок цього блока",
"paragraphs": [
"Програма, яку за традицією називають «Hello, world!», дуже проста. Вона виводить куди-небудь фразу «Hello, world!», або іншу подібну, засобами некоєї мови.",
"JavaScript — це мова, програми на якій можна виконувати в різних середовищах. У нашому випадку йдеться про браузери і серверну платформу Node.js. Якщо до цього часу ви не написали жодного рядка коду на JS і читаєте цей текст в браузері, на настільному комп'ютері, це означає, що ви буквально за кілька секунд від своєї першої JavaScript-програми."
]
},
{
"id": "4",
"type": "CODE",
"code": "<!DOCTYPE html>\n<html>\n <body>\n <p id=\"hello\"></p>\n\n <script>\n document.getElementById(\"hello\").innerHTML = \"Hello, world!\";\n </script>\n </body>\n</html>;"
},
{
"id": "5",
"type": "TEXT",
"title": "Заголовок цього блока",
"paragraphs": [
"Програма, яку традиційно називають «Hello, world!», дуже проста. Вона виводить куди-небудь фразу «Hello, world!», або іншу подібну, за допомогою засобів певної мови.",
"Існують і інші способи запуску JS-коду в браузері. Так, якщо говорити про звичайне використання програм на JavaScript, вони завантажуються в браузер для забезпечення роботи веб-сторінок. Зазвичай код форматують у вигляді окремих файлів з розширенням .js, які підключають до веб-сторінок, але програмний код можна включати і безпосередньо в код сторінки. Все це виконується за допомогою тегу <script>. Коли браузер виявляє такий код, він виконує його. Деталі про тег script можна переглянути на сайті w3school.com. Зокрема, розглянемо приклад, що демонструє роботу з веб-сторінкою за допомогою JavaScript, поданий на цьому ресурсі. Цей приклад можна запустити за допомогою ресурсу (шукайте кнопку Try it Yourself), але ми діємо трошки інакше. А саме, створимо в якомусь текстовому редакторі (наприклад, в VS Code або Notepad++) новий файл, який назвемо hello.html, і додамо до нього наступний код."
]
},
{
"id": "2",
"type": "IMAGE",
"src": "https://hsto.org/r/w1560/getpro/habr/post_images/d56/a02/ffc/d56a02ffc62949b42904ca00c63d8cc1.png",
"title": "Рисунок 1 - скриншот сайта"
},
{
"id": "3",
"type": "CODE",
"code": "const path = require('path');\n\nconst server = jsonServer.create();\n\nconst router = jsonServer.router(path.resolve(__dirname, 'db.json'));\n\nserver.use(jsonServer.defaults({}));\nserver.use(jsonServer.bodyParser);"
},
{
"id": "7",
"type": "TEXT",
"title": "Заголовок цього блока",
"paragraphs": [
"Програма, яку традиційно називають «Hello, world!», дуже проста. Вона виводить куди-небудь фразу «Hello, world!», або іншу подібну, за допомогою засобів певної мови.",
"Існують і інші способи запуску JS-коду в браузері. Так, якщо говорити про звичайне використання програм на JavaScript, вони завантажуються в браузер для забезпечення роботи веб-сторінок. Зазвичай код форматують у вигляді окремих файлів з розширенням .js, які підключають до веб-сторінок, але програмний код можна включати і безпосередньо в код сторінки. Все це виконується за допомогою тегу <script>. Коли браузер виявляє такий код, він виконує його. Деталі про тег script можна переглянути на сайті w3school.com. Зокрема, розглянемо приклад, що демонструє роботу з веб-сторінкою за допомогою JavaScript, поданий на цьому ресурсі. Цей приклад можна запустити за допомогою ресурсу (шукайте кнопку Try it Yourself), але ми діємо трошки інакше. А саме, створимо в якомусь текстовому редакторі (наприклад, в VS Code або Notepad++) новий файл, який назвемо hello.html, і додамо до нього наступний код."
]
},
{
"id": "8",
"type": "IMAGE",
"src": "https://resize.indiatvnews.com/en/centered/newbucket/1200_675/2023/06/nature-1686808887.jpg",
"title": "Рисунок 1 - скриншот сайта"
},
{
"id": "9",
"type": "TEXT",
"title": "Заголовок этого блока",
"paragraphs": [
"JavaScript — це мова, на якій можна виконувати програми в різних середовищах. У нашому випадку йдеться про браузери та серверну платформу Node.js. Якщо до цього часу ви не писали жодного рядка коду на JS і читаєте цей текст у браузері, на настільному комп'ютері, це означає, що ви буквально за кілька секунд від своєї першої JavaScript-програми."
]
}
]
}
],
"comments": [
{
"id": "1",
"body": "some comment",
"postId": "1"
"text": "some comment",
"articleId": "1",
"userId": "1"
},
{
"id": "2",
"text": "some comment 2",
"articleId": "1",
"userId": "1"
},
{
"id": "3",
"text": "some comment 3",
"articleId": "1",
"userId": "2"
}
],
"users": [
{
"id": "1",
"username": "admin",
"password": "123"
"password": "123",
"role": "ADMIN",
"avatar": "https://media.istockphoto.com/id/1300845620/ru/векторная/пользователь-icon-flat-изолирован-на-белом-фоне-символ-пользователя-иллюстрация-вектора.jpg?s=612x612&w=0&k=20&c=Po5TTi0yw6lM7qz6yay5vUbUBy3kAEWrpQmDaUMWnek="
},
{
"id": "2",
"username": "user",
"password": "123",
"role": "USER",
"avatar": "https://w7.pngwing.com/pngs/340/946/png-transparent-avatar-user-computer-icons-software-developer-avatar-child-face-heroes-thumbnail.png"
}
],
"profile": {
"userId": 1,
"id": 1,
"first": "Maksym",
"lastname": "Holonko",
"age": 19,
"currency": "UAH",
"country": "Ukraine",
"city": "Lutsk",
"username": "onvix",
"avatar": "https://media.istockphoto.com/id/1300845620/ru/векторная/пользователь-icon-flat-изолирован-на-белом-фоне-символ-пользователя-иллюстрация-вектора.jpg?s=612x612&w=0&k=20&c=Po5TTi0yw6lM7qz6yay5vUbUBy3kAEWrpQmDaUMWnek="
}
"profile": [
{
"id": "1",
"first": "Maksym",
"lastname": "Holonko",
"age": 19,
"currency": "UAH",
"country": "Ukraine",
"city": "Lutsk",
"username": "onvix",
"avatar": "https://media.istockphoto.com/id/1300845620/ru/векторная/пользователь-icon-flat-изолирован-на-белом-фоне-символ-пользователя-иллюстрация-вектора.jpg?s=612x612&w=0&k=20&c=Po5TTi0yw6lM7qz6yay5vUbUBy3kAEWrpQmDaUMWnek="
},
{
"id": "2",
"first": "Bober",
"lastname": "Bob",
"age": 23,
"currency": "EUR",
"country": "Ukraine",
"city": "Sarny",
"username": "bobrex",
"avatar": "https://w7.pngwing.com/pngs/340/946/png-transparent-avatar-user-computer-icons-software-developer-avatar-child-face-heroes-thumbnail.png"
}
]
}
4 changes: 4 additions & 0 deletions src/app/providers/StoreProvider/config/StateSchema.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { AnyAction, CombinedState, Dispatch, EnhancedStore, Reducer, ReducersMapObject } from "@reduxjs/toolkit";

Check warning on line 1 in src/app/providers/StoreProvider/config/StateSchema.ts

View workflow job for this annotation

GitHub Actions / pipeline (20.7.0)

'Dispatch' is defined but never used
import { AxiosInstance } from "axios";
import { ArticleDetailsSchema } from "entities/Article";
import { counterSchema } from "entities/Counter";
import { ProfileSchema } from "entities/Profile";
import { UserSchema } from "entities/User";
import { LoginSchema } from "features/AuthByUsername";
import { NavigateOptions, To } from "react-router-dom";
import { rtkQueryApi } from "shared/api/rtkQueryApi";

export interface StateSchema {
counter: counterSchema;
Expand All @@ -13,6 +15,8 @@ export interface StateSchema {
//асинхронні редюсери
loginForm?: LoginSchema;
profile?: ProfileSchema;
articleDetails?: ArticleDetailsSchema;
[rtkQueryApi.reducerPath]: ReturnType<typeof rtkQueryApi.reducer>
}

export type StateSchemaKey = keyof StateSchema;
Expand Down
6 changes: 4 additions & 2 deletions src/app/providers/StoreProvider/config/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { userReducer } from "entities/User";
import { createReducerManager } from "./reducerManager";
import { $api } from "shared/api/api";
import { To, NavigateOptions } from "react-router-dom";
import { rtkQueryApi } from "shared/api/rtkQueryApi";

export function createReduxStore(
initialState?: StateSchema,
Expand All @@ -16,7 +17,8 @@ export function createReduxStore(
const rootRedusers: ReducersMapObject<StateSchema> = {
...asyncReducers,
counter: counterReducer,
user: userReducer
user: userReducer,
[rtkQueryApi.reducerPath]: rtkQueryApi.reducer,
};

const reducerManager = createReducerManager(rootRedusers);
Expand All @@ -34,7 +36,7 @@ export function createReduxStore(
thunk: {
extraArgument
},
})
}).concat(rtkQueryApi.middleware),
});

// @ts-ignore

Check warning on line 42 in src/app/providers/StoreProvider/config/store.ts

View workflow job for this annotation

GitHub Actions / pipeline (20.7.0)

Use "@ts-expect-error" instead of "@ts-ignore", as "@ts-ignore" will do nothing if the following line is error-free
Expand Down
6 changes: 6 additions & 0 deletions src/entities/Article/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export { ArticleList } from "./ui/ArticleList";

export { ArticleDetails } from "./ui/ArticleDetails/ui/ArticleDetails";

export type { Article } from "./model/types/Article";
export type { ArticleDetailsSchema } from "./model/types/ArticleDetailsSchema";
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { StateSchema } from "app/providers/StoreProvider";

export const getArticleDetailsData = (state: StateSchema) => state.articleDetails?.data;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { StateSchema } from "app/providers/StoreProvider";

export const getArticleDetailsError = (state: StateSchema) => state.articleDetails?.error;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { StateSchema } from "app/providers/StoreProvider";

export const getArticleDetailsIsLoading = (state: StateSchema) => state.articleDetails?.isLoading;
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import { ThunkConfig } from "app/providers/StoreProvider";
import { Article } from "../../types/Article";
import { USER_LOCALSTORAGE_KEY } from "shared/const/localStorage";

export const fetchArticleById = createAsyncThunk<Article, string, ThunkConfig<string>>(
"profile/fetchArticleById",
async (articleId, thunkApi) => {
const { rejectWithValue, extra } = thunkApi;

try {
const token = localStorage.getItem(USER_LOCALSTORAGE_KEY) || "";
const encodedToken = encodeURIComponent(token);

const response = await extra.api.get<Article>(`/articles/${articleId}&_expand=user`, {
headers: {
"Authorization": `${encodedToken}`
}
});

if (!response.data) {
throw new Error();
}

return response.data;
} catch (error) {
console.log(error);
return rejectWithValue("error");
}
}
);
35 changes: 35 additions & 0 deletions src/entities/Article/model/slice/articleDetailsSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { ArticleDetailsSchema } from "../types/ArticleDetailsSchema";
import { Article } from "../types/Article";
import { fetchArticleById } from "../services/fetchArticlesById/fetchArticleById";

const initialState: ArticleDetailsSchema = {
};

export const articleDetailsSlice = createSlice({
name: "articleDetails",
initialState,
reducers: {
},
extraReducers: (builder) => {
builder
.addCase(fetchArticleById.pending, (state) => {
state.error = undefined;
state.isLoading = true;
})
.addCase(fetchArticleById.fulfilled, (
state,
action: PayloadAction<Article>
) => {
state.isLoading = false;
state.data = action.payload;
})
.addCase(fetchArticleById.rejected, (state, action) => {
state.isLoading = false;
state.error = action.payload;
});
}
});

export const { actions: articleDetailsActions } = articleDetailsSlice;
export const { reducer: articleDetailsReducer } = articleDetailsSlice;
54 changes: 54 additions & 0 deletions src/entities/Article/model/types/Article.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { User } from "entities/User";

export enum ArticleBlockType {
CODE = "CODE",
IMAGE = "IMAGE",
TEXT = "TEXT",
}

export enum ArticleListView {
GRID = "GRID",
COLUMN = "COLUMN"
}

export interface ArticleBlockBase {
id: string;
type: ArticleBlockType;
}

export interface ArticleCodeBlock extends ArticleBlockBase {
type: ArticleBlockType.CODE;
code: string;
}

export interface ArticleImageBlock extends ArticleBlockBase {
type: ArticleBlockType.IMAGE;
src: string,
title: string,
}

export interface ArticleTextBlock extends ArticleBlockBase {
type: ArticleBlockType.TEXT;
title?: string;
paragraphs: string[];
}

export type ArticleBlock = ArticleCodeBlock | ArticleImageBlock | ArticleTextBlock;

export enum ArticleType {
IT = "IT",
SCIENCE = "SCIENCE",
ECONOMYCS = "ECONOMYCS",
}

export interface Article {
id: string;
user: User;
title: string;
subtitle: string;
img: string;
views: number;
createdAt: string;
type: ArticleType[];
blocks: ArticleBlock[];
}
Loading

0 comments on commit 2cde7a2

Please sign in to comment.