Compare commits
No commits in common. "fix/revert-dev" and "master" have entirely different histories.
fix/revert
...
master
@ -1,5 +1,5 @@
|
|||||||
.env*
|
.env*
|
||||||
.env.production
|
!.env.production
|
||||||
node_modules
|
node_modules
|
||||||
build
|
build
|
||||||
.vscode
|
.vscode
|
||||||
|
6
.env
6
.env
@ -1,6 +0,0 @@
|
|||||||
REACT_APP_CMS_BASE_URL=http://api.scipaper.ru
|
|
||||||
REACT_APP_CMS_APP_NAME=scipaper
|
|
||||||
REACT_APP_OPENID_PROVIDER_URL=http://auth.techpal.ru/auth/realms/master/protocol/openid-connect/auth?client_id=techpal&response_type=code
|
|
||||||
REACT_APP_INTEGRATOR_URL=http://api.scipaper.ru
|
|
||||||
REACT_APP_INTEGRATOR_API_VERSION=/v1
|
|
||||||
REACT_APP_GRAPHQL_URL=/graphql
|
|
@ -1,6 +1,6 @@
|
|||||||
REACT_APP_CMS_BASE_URL=http://api.scipaper.ru
|
REACT_APP_CMS_BASE_URL=http://scipaper.ru
|
||||||
REACT_APP_CMS_APP_NAME=scipaper
|
REACT_APP_CMS_APP_NAME=scipaper
|
||||||
REACT_APP_OPENID_PROVIDER_URL=http://auth.techpal.ru/auth/realms/master/protocol/openid-connect/auth?client_id=techpal&response_type=code
|
REACT_APP_OPENID_PROVIDER_URL=http://auth.techpal.ru/auth/realms/master/protocol/openid-connect/auth?client_id=techpal&response_type=code
|
||||||
REACT_APP_INTEGRATOR_URL=http://api.scipaper.ru
|
REACT_APP_INTEGRATOR_URL=http://scipaper.ru
|
||||||
REACT_APP_INTEGRATOR_API_VERSION=/v1
|
REACT_APP_INTEGRATOR_API_VERSION=/v1
|
||||||
REACT_APP_GRAPHQL_URL=/graphql
|
REACT_APP_GRAPHQL_URL=/graphql
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
REACT_APP_CMS_BASE_URL=http://api.scipaper.ru
|
REACT_APP_CMS_BASE_URL=http://scipaper.ru
|
||||||
REACT_APP_CMS_APP_NAME=scipaper
|
REACT_APP_CMS_APP_NAME=scipaper
|
||||||
REACT_APP_OPENID_PROVIDER_URL=http://auth.techpal.ru/auth/realms/master/protocol/openid-connect/auth?client_id=techpal&response_type=code
|
REACT_APP_OPENID_PROVIDER_URL=http://auth.techpal.ru/auth/realms/master/protocol/openid-connect/auth?client_id=techpal&response_type=code
|
||||||
REACT_APP_INTEGRATOR_URL=http://api.scipaper.ru
|
REACT_APP_INTEGRATOR_URL=http://scipaper.ru
|
||||||
REACT_APP_INTEGRATOR_API_VERSION=/v1
|
REACT_APP_INTEGRATOR_API_VERSION=/v1
|
||||||
REACT_APP_GRAPHQL_URL=/graphql
|
REACT_APP_GRAPHQL_URL=/graphql
|
||||||
|
56
Dockerfile
56
Dockerfile
@ -1,43 +1,41 @@
|
|||||||
# Install dependencies of project
|
# Install dependencies only when needed
|
||||||
FROM node:fermium-alpine AS dependencies
|
FROM node:16-alpine AS deps
|
||||||
|
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||||
RUN apk add --no-cache libc6-compat
|
RUN apk add --no-cache libc6-compat
|
||||||
WORKDIR /home/app/
|
WORKDIR /app
|
||||||
COPY package.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm i
|
RUN npm ci
|
||||||
|
|
||||||
|
# Rebuild the source code only when needed
|
||||||
# Build application to bunch of static files
|
FROM node:16-alpine AS builder
|
||||||
FROM node:fermium-alpine AS builder
|
ENV NODE_ENV production
|
||||||
WORKDIR /home/app/
|
WORKDIR /app
|
||||||
COPY --from=dependencies ./home/app/node_modules ./node_modules
|
# Copy dependencies from deps stage
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
# Bundle static assets with nginx
|
||||||
# NGINX image
|
FROM node:16-alpine as production
|
||||||
FROM nginx:1.21.6 as production
|
|
||||||
# Copy built assets from builder
|
# Copy built assets from builder
|
||||||
COPY --from=builder /home/app/build /usr/share/nginx/html
|
WORKDIR /app
|
||||||
# Add nginx.config
|
COPY --from=builder /app/build .
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
|
||||||
# Copy setup nginx entrypoint file
|
|
||||||
COPY ./scripts/entrypoint.sh .
|
|
||||||
|
|
||||||
# Expose ports
|
# Expose ports
|
||||||
EXPOSE 80
|
EXPOSE 3000
|
||||||
|
|
||||||
# Execute script
|
COPY .env.production .
|
||||||
RUN chmod +x ./entrypoint.sh
|
|
||||||
|
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
# ENV USER_NAME=node_user USER_UID=2000 GROUP_NAME=node_group GROUP_UID=2000
|
ENV USER_NAME=node_user USER_UID=2000 GROUP_NAME=node_group GROUP_UID=2000
|
||||||
|
|
||||||
# RUN deluser --remove-home node \
|
RUN npm i -g serve \
|
||||||
# && addgroup --g ${GROUP_UID} -S ${GROUP_NAME} \
|
&& deluser --remove-home node \
|
||||||
# && adduser -D -S -s /sbin/nologin -u ${USER_UID} -G ${GROUP_NAME} ${USER_NAME}
|
&& addgroup --g ${GROUP_UID} -S ${GROUP_NAME} \
|
||||||
# USER "${USER_NAME}"
|
&& adduser -D -S -s /sbin/nologin -u ${USER_UID} -G ${GROUP_NAME} ${USER_NAME}\
|
||||||
|
&& chown -R ${USER_NAME}:${GROUP_NAME} "/app/"
|
||||||
|
|
||||||
ENTRYPOINT ["./entrypoint.sh"]
|
|
||||||
|
|
||||||
# Start serving
|
USER "${USER_NAME}"
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD serve -s .
|
@ -1,9 +1,6 @@
|
|||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
# no verbose
|
# no verbose
|
||||||
set +x
|
set +x
|
||||||
# save env to file
|
|
||||||
printenv > .env.production
|
|
||||||
|
|
||||||
# config
|
# config
|
||||||
envFilename='.env.production'
|
envFilename='.env.production'
|
||||||
resolvingPath='/usr/share/nginx/html'
|
resolvingPath='/usr/share/nginx/html'
|
8063
package-lock.json
generated
8063
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@
|
|||||||
"@fortawesome/free-brands-svg-icons": "^6.2.0",
|
"@fortawesome/free-brands-svg-icons": "^6.2.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.3",
|
"@headlessui/react": "^1.6.6",
|
||||||
"@reduxjs/toolkit": "^1.8.3",
|
"@reduxjs/toolkit": "^1.8.3",
|
||||||
"@types/node": "^16.11.47",
|
"@types/node": "^16.11.47",
|
||||||
"@types/react": "^18.0.15",
|
"@types/react": "^18.0.15",
|
||||||
@ -24,9 +24,9 @@
|
|||||||
"i18next-http-backend": "^1.4.1",
|
"i18next-http-backend": "^1.4.1",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.1.0",
|
"react": "^18.2.0",
|
||||||
"react-copy-to-clipboard": "^5.1.0",
|
"react-copy-to-clipboard": "^5.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hotkeys": "^2.0.0",
|
"react-hotkeys": "^2.0.0",
|
||||||
"react-i18next": "^11.18.3",
|
"react-i18next": "^11.18.3",
|
||||||
"react-loading-skeleton": "^3.1.0",
|
"react-loading-skeleton": "^3.1.0",
|
||||||
|
33
src/article/controller/articleViewModel.ts
Executable file → Normal file
33
src/article/controller/articleViewModel.ts
Executable file → Normal file
@ -1,31 +1,22 @@
|
|||||||
import type { ArticleStore } from "../domain/articleStore";
|
import type { ArticleStore } from "../domain/articleStore";
|
||||||
|
import { getArticleUseCase } from "../useCases/getArticleUseCase";
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
import { GetArticleUseCase } from "article/useCases/getArticleUseCase";
|
|
||||||
import { FetchArticleUseCase } from "article/useCases/fetchArticleUseCase";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
|
|
||||||
function useArticleViewModel(
|
function useArticleViewModel(store: ArticleStore) {
|
||||||
store: ArticleStore,
|
const _getArticle = useCallback(
|
||||||
fetchArticleUseCase: FetchArticleUseCase,
|
(id: string) => getArticleUseCase(store.getArticle, store.setArticle, id),
|
||||||
getArticleUseCase: GetArticleUseCase,
|
[store.getArticle, store.setArticle]
|
||||||
) {
|
);
|
||||||
const { id } = useParams();
|
|
||||||
|
|
||||||
// const getArticle = useCallback(
|
|
||||||
// () => {
|
|
||||||
// getArticleUseCase.call(id ?? '').catch((_) => fetchArticleUseCase.call(id ?? ''));
|
|
||||||
// console.log(id);
|
|
||||||
// },
|
|
||||||
// [id]
|
|
||||||
// );
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getArticleUseCase.call(id ?? '').catch((_) => fetchArticleUseCase.call(id ?? ''));
|
if (store.article != undefined) {
|
||||||
}, []);
|
_getArticle(store.article.id);
|
||||||
|
}
|
||||||
|
}, [store.article?.id]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
article: store.currentArticle,
|
article: store.article,
|
||||||
shouldShowLoading: store.isLoading,
|
shouldShowLoading: typeof store.article === "undefined" || store.isLoading,
|
||||||
hasError: store.hasError,
|
hasError: store.hasError,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
4
src/article/data/articleAPIService.ts
Executable file → Normal file
4
src/article/data/articleAPIService.ts
Executable file → Normal file
@ -7,7 +7,7 @@ import { integratorApiClient } from "core/httpClient";
|
|||||||
|
|
||||||
const articleEndpoint = "/papers/"
|
const articleEndpoint = "/papers/"
|
||||||
|
|
||||||
async function fetchArticle(id: string): Promise<Article> {
|
async function getArticle(id: string): Promise<Article> {
|
||||||
try {
|
try {
|
||||||
const response = await integratorApiClient.get<FetchArticleByIdDTO>(
|
const response = await integratorApiClient.get<FetchArticleByIdDTO>(
|
||||||
// `https://run.mocky.io/v3/62cd4581-d864-4d46-b1d6-02b45b5d1994/${id}`
|
// `https://run.mocky.io/v3/62cd4581-d864-4d46-b1d6-02b45b5d1994/${id}`
|
||||||
@ -33,4 +33,4 @@ async function fetchArticle(id: string): Promise<Article> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { fetchArticle };
|
export { getArticle };
|
||||||
|
0
src/article/data/articleActionTypes.ts
Executable file → Normal file
0
src/article/data/articleActionTypes.ts
Executable file → Normal file
26
src/article/data/articleActions.ts
Executable file → Normal file
26
src/article/data/articleActions.ts
Executable file → Normal file
@ -1,25 +1,23 @@
|
|||||||
import type { Article } from "../domain/articleEntity";
|
import type { Article } from "../domain/articleEntity";
|
||||||
|
import { getArticle as getArticleAPI } from "./articleAPIService";
|
||||||
import * as actionTypes from "./articleActionTypes";
|
import * as actionTypes from "./articleActionTypes";
|
||||||
import { dispatchStatus } from "../../store/index";
|
import { dispatchStatus } from "../../store/index";
|
||||||
|
|
||||||
const setArticleAction = (article: Article, articlesList: Array<Article>) => (dispatch: any) => {
|
const setArticleAction = (article: Article) => (dispatch: any) =>
|
||||||
if (!articlesList.includes(article)) {
|
dispatch({ type: actionTypes.SET_ARTICLE, article });
|
||||||
const updatedList = articlesList.concat([article]);
|
|
||||||
dispatch({ type: actionTypes.SET_ARTICLE, article, updatedList });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getArticleAction = (id: string, articles: Array<Article>) => (dispatch: any) => {
|
const getArticleAction = (id: string) => (dispatch: any) => {
|
||||||
const filteredArray = articles.filter((e) => e.id == id);
|
dispatch({ type: actionTypes.GET_ARTICLE });
|
||||||
if (filteredArray.length > 0) {
|
|
||||||
const article = filteredArray[0];
|
return getArticleAPI(id)
|
||||||
|
.then((article) => {
|
||||||
dispatchStatus(actionTypes.GET_ARTICLE, ".success", article)(dispatch);
|
dispatchStatus(actionTypes.GET_ARTICLE, ".success", article)(dispatch);
|
||||||
return article;
|
return article;
|
||||||
}
|
})
|
||||||
|
.catch((reason) => {
|
||||||
const reason = 'Article not in the store';
|
|
||||||
dispatchStatus(actionTypes.GET_ARTICLE, ".failure", reason)(dispatch);
|
dispatchStatus(actionTypes.GET_ARTICLE, ".failure", reason)(dispatch);
|
||||||
return null;
|
return reason;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { setArticleAction, getArticleAction };
|
export { setArticleAction, getArticleAction };
|
||||||
|
45
src/article/data/articleCommonStateStore.ts
Executable file → Normal file
45
src/article/data/articleCommonStateStore.ts
Executable file → Normal file
@ -2,58 +2,37 @@ import React, { useCallback, useState } from "react";
|
|||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { ArticleStore } from "../domain/articleStore";
|
import { ArticleStore } from "../domain/articleStore";
|
||||||
import type { Article } from "../domain/articleEntity";
|
import type { Article } from "../domain/articleEntity";
|
||||||
|
import { getArticle as getArticleAPI } from "./articleAPIService";
|
||||||
|
|
||||||
const useArticleCommonStore = (): ArticleStore => {
|
const useArticleCommonStore = (): ArticleStore => {
|
||||||
const [isLoading, setLoading] = useState<boolean>(false);
|
const [isLoading, setLoading] = useState<boolean>(false);
|
||||||
const [hasError, setError] = useState<boolean>(false);
|
const [hasError, setError] = useState<boolean>(false);
|
||||||
const [currentArticle, setCurrentArticle] = useState<Article | null>(null);
|
const [article, setArticleState] = useState<Article | undefined>();
|
||||||
const [articles, setArticlesState] = useState<Array<Article>>([]);
|
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const getArticle = useCallback(
|
const getArticle = useCallback(
|
||||||
(id: string) => {
|
async (id: string) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (typeof currentArticle === undefined) {
|
try {
|
||||||
const fromStore = findArticleFromStore(id);
|
const article = await getArticleAPI(id);
|
||||||
if (typeof fromStore === undefined) {
|
setArticleState(article);
|
||||||
|
setLoading(false);
|
||||||
|
return article;
|
||||||
|
} catch (error) {
|
||||||
setError(true);
|
setError(true);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
setCurrentArticle(fromStore);
|
|
||||||
return fromStore;
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
return currentArticle;
|
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const findArticleFromStore = (id: string): Article | null => {
|
|
||||||
const filteredArray = articles.filter((e) => e.id == id);
|
|
||||||
if (filteredArray.length > 0) {
|
|
||||||
return filteredArray[0];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const setNewArticle = (newArticle: Article) => {
|
|
||||||
setCurrentArticle(newArticle);
|
|
||||||
if (!articles.includes(newArticle)) {
|
|
||||||
setArticlesState(articles.concat([newArticle]));
|
|
||||||
} else if (articles.length == 0) {
|
|
||||||
setArticlesState([newArticle]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
articles: articles,
|
article: article,
|
||||||
currentArticle: currentArticle,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
hasError,
|
hasError,
|
||||||
setArticle: setNewArticle,
|
setArticle: setArticleState,
|
||||||
getArticle: getArticle,
|
getArticle,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
7
src/article/data/articleReducer.ts
Executable file → Normal file
7
src/article/data/articleReducer.ts
Executable file → Normal file
@ -5,8 +5,7 @@ import * as actionTypes from "./articleActionTypes";
|
|||||||
type ArticleStoreState = Omit<ArticleStore, "getArticle" | "setArticle">;
|
type ArticleStoreState = Omit<ArticleStore, "getArticle" | "setArticle">;
|
||||||
|
|
||||||
const INITIAL_STATE: ArticleStoreState = {
|
const INITIAL_STATE: ArticleStoreState = {
|
||||||
articles: [],
|
article: undefined,
|
||||||
currentArticle: null,
|
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
hasError: false,
|
hasError: false,
|
||||||
};
|
};
|
||||||
@ -17,11 +16,11 @@ const articleReducer = (
|
|||||||
): ArticleStoreState => {
|
): ArticleStoreState => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case actionTypes.SET_ARTICLE:
|
case actionTypes.SET_ARTICLE:
|
||||||
return { ...state, articles: action.updatedList, currentArticle: action.article, hasError: typeof action.article === undefined };
|
return { ...state, article: action.article };
|
||||||
case actionTypes.GET_ARTICLE:
|
case actionTypes.GET_ARTICLE:
|
||||||
return { ...state, isLoading: true };
|
return { ...state, isLoading: true };
|
||||||
case actionTypes.GET_ARTICLE_SUCCESS:
|
case actionTypes.GET_ARTICLE_SUCCESS:
|
||||||
return { ...state, isLoading: false, currentArticle: action.payload };
|
return { ...state, isLoading: false, article: action.payload };
|
||||||
case actionTypes.GET_ARTICLE_FAILURE:
|
case actionTypes.GET_ARTICLE_FAILURE:
|
||||||
return { ...state, hasError: true, isLoading: false };
|
return { ...state, hasError: true, isLoading: false };
|
||||||
default:
|
default:
|
||||||
|
9
src/article/data/articleStoreImplementation.ts
Executable file → Normal file
9
src/article/data/articleStoreImplementation.ts
Executable file → Normal file
@ -9,23 +9,22 @@ import { RootState, useAppSelector } from "store";
|
|||||||
const articleSelector = (state: RootState): ArticleStoreState => state.article;
|
const articleSelector = (state: RootState): ArticleStoreState => state.article;
|
||||||
|
|
||||||
const useArticleStore = (): ArticleStore => {
|
const useArticleStore = (): ArticleStore => {
|
||||||
const { isLoading, hasError, currentArticle, articles } = useAppSelector(articleSelector);
|
const { isLoading, article, hasError } = useAppSelector(articleSelector);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const setArticle = useCallback(
|
const setArticle = useCallback(
|
||||||
(article: Article) => setArticleAction(article, articles)(dispatch),
|
(article: Article) => setArticleAction(article)(dispatch),
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getArticle = useCallback(
|
const getArticle = useCallback(
|
||||||
(id: string) => getArticleAction(id, articles)(dispatch),
|
(id: string) => getArticleAction(id)(dispatch),
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
articles: articles,
|
article: article,
|
||||||
currentArticle: currentArticle,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
hasError,
|
hasError,
|
||||||
setArticle,
|
setArticle,
|
||||||
|
0
src/article/data/dto/fetch_article_by_id_dto.ts
Executable file → Normal file
0
src/article/data/dto/fetch_article_by_id_dto.ts
Executable file → Normal file
0
src/article/domain/articleEntity.ts
Executable file → Normal file
0
src/article/domain/articleEntity.ts
Executable file → Normal file
0
src/article/domain/articleModel.ts
Executable file → Normal file
0
src/article/domain/articleModel.ts
Executable file → Normal file
7
src/article/domain/articleStore.ts
Executable file → Normal file
7
src/article/domain/articleStore.ts
Executable file → Normal file
@ -1,14 +1,13 @@
|
|||||||
import { Article } from './articleEntity';
|
import { Article } from './articleEntity';
|
||||||
interface ArticleStore {
|
interface ArticleStore {
|
||||||
// State
|
// State
|
||||||
articles: Array<Article>;
|
article: Article | undefined;
|
||||||
currentArticle: Article | null;
|
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
setArticle(article: Article): void;
|
setArticle(article?: Article): void;
|
||||||
getArticle(identifier: string): Article | null;
|
getArticle(identifier: string): Promise<Article | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { ArticleStore };
|
export type { ArticleStore };
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import { Article } from "article/domain/articleEntity";
|
|
||||||
import { ArticleStore } from "article/domain/articleStore";
|
|
||||||
|
|
||||||
|
|
||||||
class FetchArticleUseCase {
|
|
||||||
/* ------------------------------ Dependencies ------------------------------ */
|
|
||||||
_fetchArticleCallback: (id: string) => Promise<Article | null>;
|
|
||||||
_store: ArticleStore;
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
constructor(
|
|
||||||
fetchArticle: (id: string) => Promise<Article | null>,
|
|
||||||
store: ArticleStore,
|
|
||||||
) {
|
|
||||||
this._fetchArticleCallback = fetchArticle;
|
|
||||||
this._store = store;
|
|
||||||
}
|
|
||||||
/* ----------------------------- Implementation ----------------------------- */
|
|
||||||
async call(id: string): Promise<Article | null> {
|
|
||||||
return this._fetchArticleCallback(id).then((article) => {
|
|
||||||
if (article != null) {
|
|
||||||
this._store.setArticle(article);
|
|
||||||
}
|
|
||||||
return article;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
}
|
|
||||||
|
|
||||||
export { FetchArticleUseCase };
|
|
31
src/article/useCases/getArticleUseCase.ts
Executable file → Normal file
31
src/article/useCases/getArticleUseCase.ts
Executable file → Normal file
@ -1,25 +1,16 @@
|
|||||||
import { Article } from "article/domain/articleEntity";
|
import { Article } from "article/domain/articleEntity";
|
||||||
import type { ArticleStore } from "../domain/articleStore";
|
import type { ArticleStore } from "../domain/articleStore";
|
||||||
|
|
||||||
class GetArticleUseCase {
|
const getArticleUseCase = async (
|
||||||
/* ------------------------------ Dependencies ------------------------------ */
|
getArticle: ArticleStore["getArticle"],
|
||||||
_store: ArticleStore;
|
setArticle: ArticleStore["setArticle"],
|
||||||
/* -------------------------------------------------------------------------- */
|
id: Article["id"]
|
||||||
constructor(
|
): Promise<Article | null> => {
|
||||||
store: ArticleStore,
|
const article = await getArticle(id);
|
||||||
) {
|
if (article) {
|
||||||
this._store = store;
|
await setArticle(article);
|
||||||
}
|
|
||||||
/* ----------------------------- Implementation ----------------------------- */
|
|
||||||
async call(id: string): Promise<Article> {
|
|
||||||
const storedArticle = this._store.getArticle(id);
|
|
||||||
if (storedArticle != null) {
|
|
||||||
this._store.setArticle(storedArticle);
|
|
||||||
return storedArticle;
|
|
||||||
}
|
|
||||||
throw new Error('Article not found');
|
|
||||||
}
|
|
||||||
/* -------------------------------------------------------------------------- */
|
|
||||||
}
|
}
|
||||||
|
return article;
|
||||||
|
};
|
||||||
|
|
||||||
export { GetArticleUseCase };
|
export { getArticleUseCase };
|
||||||
|
0
src/article/useCases/params/create_article_params.ts
Executable file → Normal file
0
src/article/useCases/params/create_article_params.ts
Executable file → Normal file
0
src/assets/lotties/notFoundAnimation.json
Executable file → Normal file
0
src/assets/lotties/notFoundAnimation.json
Executable file → Normal file
0
src/components/Article/ArticleParts/InteractionButtons/ArticleShareButton.tsx
Executable file → Normal file
0
src/components/Article/ArticleParts/InteractionButtons/ArticleShareButton.tsx
Executable file → Normal file
12
src/components/ArticleSearchResult.tsx
Executable file → Normal file
12
src/components/ArticleSearchResult.tsx
Executable file → Normal file
@ -27,15 +27,17 @@ export const ArticleSearchResult = ({ searchItem }: Props) => {
|
|||||||
<Article className=" pt-6 pb-3 ">
|
<Article className=" pt-6 pb-3 ">
|
||||||
<div className="flex flex-row justify-between">
|
<div className="flex flex-row justify-between">
|
||||||
<Article.Breadcumbs emphasis="low" className="flex flex-wrap flex-row">
|
<Article.Breadcumbs emphasis="low" className="flex flex-wrap flex-row">
|
||||||
{[`${searchItem.topic}`]}
|
{[
|
||||||
|
`${searchItem.topic}`,
|
||||||
|
`${searchItem.topic}`,
|
||||||
|
`${searchItem.topic}`,
|
||||||
|
`${searchItem.topic}`,
|
||||||
|
]}
|
||||||
</Article.Breadcumbs>
|
</Article.Breadcumbs>
|
||||||
<Article.SubscriptionsButtons />
|
<Article.SubscriptionsButtons />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Article.Title
|
<Article.Title linkTo={`/article/info/${searchItem.id}`} className="text-2xl">
|
||||||
linkTo={`/article/info/${searchItem.id}`}
|
|
||||||
className="text-2xl"
|
|
||||||
>
|
|
||||||
{searchItem.title}
|
{searchItem.title}
|
||||||
</Article.Title>
|
</Article.Title>
|
||||||
<Article.Authors emphasis="low" className="flex flex-wrap flex-row">
|
<Article.Authors emphasis="low" className="flex flex-wrap flex-row">
|
||||||
|
26
src/components/Burger.tsx
Executable file → Normal file
26
src/components/Burger.tsx
Executable file → Normal file
@ -21,12 +21,10 @@ import { ReactComponent as SVGFile } from "assets/svg/file.svg";
|
|||||||
import { ReactComponent as SVGEye } from "assets/svg/eye.svg";
|
import { ReactComponent as SVGEye } from "assets/svg/eye.svg";
|
||||||
import { ReactComponent as SVGArrowUp } from "assets/svg/arrow-up.svg";
|
import { ReactComponent as SVGArrowUp } from "assets/svg/arrow-up.svg";
|
||||||
import { ReactComponent as SVGCaretDown } from "assets/svg/caret-down.svg";
|
import { ReactComponent as SVGCaretDown } from "assets/svg/caret-down.svg";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
type Props = React.ComponentPropsWithoutRef<"div">;
|
type Props = React.ComponentPropsWithoutRef<"div">;
|
||||||
|
|
||||||
const Burger = (props: Props) => {
|
const Burger = (props: Props) => {
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
return (
|
return (
|
||||||
<div {...props}>
|
<div {...props}>
|
||||||
<Menu as="div" className="relative inline-block text-left z-30">
|
<Menu as="div" className="relative inline-block text-left z-30">
|
||||||
@ -48,15 +46,13 @@ const Burger = (props: Props) => {
|
|||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<Menu.Items
|
<Menu.Items
|
||||||
className="origin-top-right absolute right-0 mt-5 w-48 rounded-md
|
className="origin-top-right absolute right-0 mt-5 w-44 rounded-md
|
||||||
shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
|
shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||||
>
|
>
|
||||||
<div className="py-1">
|
<div className="py-1">
|
||||||
<Disclosure>
|
<Disclosure>
|
||||||
<Disclosure.Button className="uppercase text-base px-2 py-1">
|
<Disclosure.Button className="uppercase text-base px-2 py-1">
|
||||||
<Link className="text-[#096DD9]">
|
<Link className="text-[#096DD9]">create new</Link>
|
||||||
{t("navbar.createNew")}
|
|
||||||
</Link>
|
|
||||||
</Disclosure.Button>
|
</Disclosure.Button>
|
||||||
</Disclosure>
|
</Disclosure>
|
||||||
<hr />
|
<hr />
|
||||||
@ -70,7 +66,7 @@ const Burger = (props: Props) => {
|
|||||||
text-base
|
text-base
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span>{t("navbar.library.navTitle")}</span>
|
my library
|
||||||
<SVGArrowUp
|
<SVGArrowUp
|
||||||
className={`${
|
className={`${
|
||||||
open ? "rotate-180 transform" : "rotate-360"
|
open ? "rotate-180 transform" : "rotate-360"
|
||||||
@ -87,7 +83,7 @@ const Burger = (props: Props) => {
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<SVGFile className="stroke-black w-4 h-4" />
|
<SVGFile className="stroke-black w-4 h-4" />
|
||||||
<span>{t("navbar.library.publications")}</span>
|
My Publications
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
@ -100,7 +96,7 @@ const Burger = (props: Props) => {
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<SVGFavoriteOutlined className="stroke-black w-4 h-4" />
|
<SVGFavoriteOutlined className="stroke-black w-4 h-4" />
|
||||||
<span>{t("navbar.library.favorites")}</span>
|
My Favorites
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
@ -113,7 +109,7 @@ const Burger = (props: Props) => {
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<SVGFolder className="stroke-black fill-black w-4 h-4" />
|
<SVGFolder className="stroke-black fill-black w-4 h-4" />
|
||||||
<span>{t("navbar.library.collections")}</span>
|
My Collections
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
@ -126,7 +122,7 @@ const Burger = (props: Props) => {
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<SVGEye className="stroke-black w-4 h-4" />
|
<SVGEye className="stroke-black w-4 h-4" />
|
||||||
<span>{t("navbar.library.recentViewed")}</span>
|
Recent Viewed
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
@ -144,7 +140,7 @@ const Burger = (props: Props) => {
|
|||||||
text-base
|
text-base
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span>{t("navbar.about.navTitle")}</span>
|
About
|
||||||
<SVGArrowUp
|
<SVGArrowUp
|
||||||
className={`${
|
className={`${
|
||||||
open ? "rotate-180 transform" : "rotate-360"
|
open ? "rotate-180 transform" : "rotate-360"
|
||||||
@ -160,7 +156,7 @@ const Burger = (props: Props) => {
|
|||||||
text-base
|
text-base
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span>{t("navbar.about.aboutProject")}</span>
|
About Freeland
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
@ -172,7 +168,7 @@ const Burger = (props: Props) => {
|
|||||||
text-base
|
text-base
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span>{t("navbar.about.contacts")}</span>
|
Contact Us
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
@ -184,7 +180,7 @@ const Burger = (props: Props) => {
|
|||||||
text-base
|
text-base
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span>{t("navbar.about.help")}</span>
|
Help
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
|
0
src/components/Disclosure.tsx
Executable file → Normal file
0
src/components/Disclosure.tsx
Executable file → Normal file
0
src/components/Filters/AppiledFilters.tsx
Executable file → Normal file
0
src/components/Filters/AppiledFilters.tsx
Executable file → Normal file
0
src/components/Filters/Filter.tsx
Executable file → Normal file
0
src/components/Filters/Filter.tsx
Executable file → Normal file
0
src/components/Filters/IProdutct.ts
Executable file → Normal file
0
src/components/Filters/IProdutct.ts
Executable file → Normal file
0
src/components/Filters/SearchFilterBar.tsx
Executable file → Normal file
0
src/components/Filters/SearchFilterBar.tsx
Executable file → Normal file
0
src/components/Filters/functions/debounce.ts
Executable file → Normal file
0
src/components/Filters/functions/debounce.ts
Executable file → Normal file
0
src/components/Loader/Loader.css
Executable file → Normal file
0
src/components/Loader/Loader.css
Executable file → Normal file
0
src/components/Loader/Loader.tsx
Executable file → Normal file
0
src/components/Loader/Loader.tsx
Executable file → Normal file
0
src/components/LocalizationButton.tsx
Executable file → Normal file
0
src/components/LocalizationButton.tsx
Executable file → Normal file
5
src/components/Markdown.tsx
Executable file → Normal file
5
src/components/Markdown.tsx
Executable file → Normal file
@ -14,8 +14,9 @@ import Heading from "./typography/Heading";
|
|||||||
/* Code */
|
/* Code */
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
import { oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
|
import { dark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||||
import Link from "./typography/Link";
|
import Link from "./typography/Link";
|
||||||
|
import style from "react-syntax-highlighter/dist/esm/styles/hljs/a11y-dark";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
markdown: string;
|
markdown: string;
|
||||||
@ -152,7 +153,7 @@ const Markdown = ({ markdown }: Props) => {
|
|||||||
return !inline && match ? (
|
return !inline && match ? (
|
||||||
<SyntaxHighlighter
|
<SyntaxHighlighter
|
||||||
children={String(children).replace(/\n$/, "")}
|
children={String(children).replace(/\n$/, "")}
|
||||||
style={oneLight}
|
style={dark}
|
||||||
language={match[1]}
|
language={match[1]}
|
||||||
PreTag="div"
|
PreTag="div"
|
||||||
/>
|
/>
|
||||||
|
0
src/components/SearchResultsSection.tsx
Executable file → Normal file
0
src/components/SearchResultsSection.tsx
Executable file → Normal file
0
src/components/SearchSection.tsx
Executable file → Normal file
0
src/components/SearchSection.tsx
Executable file → Normal file
@ -83,7 +83,7 @@ function Select<T>({
|
|||||||
}: Props<T>): JSX.Element {
|
}: Props<T>): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div className={classNames("top-16 w-60", className)}>
|
<div className={classNames("top-16 w-60", className)}>
|
||||||
<Listbox value={value as any } {...props} onChange={onChange}>
|
<Listbox value={value} {...props} onChange={onChange}>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Listbox.Button
|
<Listbox.Button
|
||||||
className={classNames([
|
className={classNames([
|
||||||
|
17
src/components/fetchAnArticle/AnArticle.tsx
Executable file → Normal file
17
src/components/fetchAnArticle/AnArticle.tsx
Executable file → Normal file
@ -10,20 +10,19 @@ import { SVGSearch } from "components/icons";
|
|||||||
import BaseLayout from "components/BaseLayout";
|
import BaseLayout from "components/BaseLayout";
|
||||||
import Typography from "components/typography/Typography";
|
import Typography from "components/typography/Typography";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FetchArticleUseCase } from "article/useCases/fetchArticleUseCase";
|
|
||||||
import { GetArticleUseCase } from "article/useCases/getArticleUseCase";
|
|
||||||
import { fetchArticle } from "article/data/articleAPIService";
|
|
||||||
|
|
||||||
const AnArticle = () => {
|
const AnArticle = () => {
|
||||||
const store = useArticleStore();
|
const store = useArticleStore();
|
||||||
const { article, hasError, shouldShowLoading } = useArticleViewModel(
|
const { article, hasError, shouldShowLoading } = useArticleViewModel(store);
|
||||||
store,
|
|
||||||
new FetchArticleUseCase(fetchArticle, store),
|
|
||||||
new GetArticleUseCase(store),
|
|
||||||
);
|
|
||||||
|
|
||||||
const { i18n, t } = useTranslation();
|
const { i18n, t } = useTranslation();
|
||||||
|
|
||||||
|
const { id } = useParams();
|
||||||
|
const newId = `${id}`;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
store.getArticle(newId);
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
if (hasError) {
|
if (hasError) {
|
||||||
return <NotFound />;
|
return <NotFound />;
|
||||||
}
|
}
|
||||||
|
14
src/components/fetchAnArticle/AnArticleBody.tsx
Executable file → Normal file
14
src/components/fetchAnArticle/AnArticleBody.tsx
Executable file → Normal file
@ -13,17 +13,15 @@ import BaseLayout from "components/BaseLayout";
|
|||||||
import Container from "components/Container";
|
import Container from "components/Container";
|
||||||
import NotFound from "./NotFound";
|
import NotFound from "./NotFound";
|
||||||
import Markdown from "components/Markdown";
|
import Markdown from "components/Markdown";
|
||||||
import { FetchArticleUseCase } from "article/useCases/fetchArticleUseCase";
|
|
||||||
import { GetArticleUseCase } from "article/useCases/getArticleUseCase";
|
|
||||||
import { fetchArticle } from "article/data/articleAPIService";
|
|
||||||
|
|
||||||
const AnArticleBody = () => {
|
const AnArticleBody = () => {
|
||||||
const store = useArticleStore();
|
const store = useArticleStore();
|
||||||
const { article, hasError, shouldShowLoading } = useArticleViewModel(
|
const { article, hasError, shouldShowLoading } = useArticleViewModel(store);
|
||||||
store,
|
const { id } = useParams();
|
||||||
new FetchArticleUseCase(fetchArticle, store),
|
const newId = `${id}`;
|
||||||
new GetArticleUseCase(store),
|
useEffect(() => {
|
||||||
);
|
store.getArticle(newId);
|
||||||
|
}, [id]);
|
||||||
if (hasError) <NotFound />;
|
if (hasError) <NotFound />;
|
||||||
return (
|
return (
|
||||||
<BaseLayout>
|
<BaseLayout>
|
||||||
|
0
src/components/fetchAnArticle/AskeletonArticle.tsx
Executable file → Normal file
0
src/components/fetchAnArticle/AskeletonArticle.tsx
Executable file → Normal file
0
src/components/fetchAnArticle/NotFound.tsx
Executable file → Normal file
0
src/components/fetchAnArticle/NotFound.tsx
Executable file → Normal file
@ -80,7 +80,7 @@ const Header = () => {
|
|||||||
<ContextMenu
|
<ContextMenu
|
||||||
emphasis="high"
|
emphasis="high"
|
||||||
button={t("navbar.library.navTitle")}
|
button={t("navbar.library.navTitle")}
|
||||||
className="border-none uppercase z-40"
|
className="border-none uppercase"
|
||||||
>
|
>
|
||||||
<ContextMenuAction
|
<ContextMenuAction
|
||||||
caption={t("navbar.library.publications")}
|
caption={t("navbar.library.publications")}
|
||||||
@ -112,7 +112,7 @@ const Header = () => {
|
|||||||
<ContextMenu
|
<ContextMenu
|
||||||
emphasis="high"
|
emphasis="high"
|
||||||
button={t("navbar.about.navTitle")}
|
button={t("navbar.about.navTitle")}
|
||||||
className="border-none uppercase z-40"
|
className="border-none uppercase"
|
||||||
>
|
>
|
||||||
<ContextMenuAction
|
<ContextMenuAction
|
||||||
caption={t("navbar.about.aboutProject")}
|
caption={t("navbar.about.aboutProject")}
|
||||||
|
0
src/components/search/SearchBar.tsx
Executable file → Normal file
0
src/components/search/SearchBar.tsx
Executable file → Normal file
@ -47,7 +47,6 @@ export default function Link({
|
|||||||
typeof style === "function"
|
typeof style === "function"
|
||||||
? style({
|
? style({
|
||||||
isActive: true,
|
isActive: true,
|
||||||
isPending: false
|
|
||||||
})
|
})
|
||||||
: style
|
: style
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ export function RouterLink({
|
|||||||
return (
|
return (
|
||||||
<NavLink
|
<NavLink
|
||||||
to={to}
|
to={to}
|
||||||
className={classNames({ "pointer-events-none": disabled }, className as string)}
|
className={classNames({ "pointer-events-none": disabled }, className)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -25,6 +25,7 @@ if (!rootElement) throw new Error("Failed to find the root element");
|
|||||||
const root = ReactDOM.createRoot(rootElement);
|
const root = ReactDOM.createRoot(rootElement);
|
||||||
root.render(
|
root.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
|
<React.StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<App />} />
|
<Route path="/" element={<App />} />
|
||||||
@ -45,6 +46,7 @@ root.render(
|
|||||||
<Route path="/*" element={<NotFound />}></Route>
|
<Route path="/*" element={<NotFound />}></Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
|
</React.StrictMode>
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
0
src/pages/SearchResultsPage.tsx
Executable file → Normal file
0
src/pages/SearchResultsPage.tsx
Executable file → Normal file
Loading…
x
Reference in New Issue
Block a user