Skip to content

Commit 2fe6bae

Browse files
authored
Merge branch 'dev' into feature/enhance/repo-readme
2 parents 0a02122 + 1d2c9d6 commit 2fe6bae

File tree

39 files changed

+310
-85
lines changed

39 files changed

+310
-85
lines changed

.env

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
SKIP_PREFLIGHT_CHECK=true
22
API_GENCOMMENT=/** @generated THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. */
33

4-
REACT_APP_API_URL=https://api.github.com/graphql
5-
# Используется для кодогенерации, Apollo Client плагина
6-
REACT_APP_ACCESS_TOKEN=9221b1b5ba4b0de72eed80b09602ed7b4c73c222
74

85
# Firebase section
96
REACT_APP_FIREBASE_apiKey=AIzaSyABlBQc-tjCRKWwBj8jTTrMiT2M2UKiJpk
@@ -15,3 +12,12 @@ REACT_APP_FIREBASE_messagingSenderId=14406286286
1512
REACT_APP_FIREBASE_appId=1:14406286286:web:58c7c11c2762d36a55c99f
1613

1714
REACT_APP_DEV_STORAGE_URL=https://dev.github-client.gq/dev/temp-stands.html
15+
16+
# Github links
17+
REACT_APP_API_URL=https://api.github.com/graphql
18+
REACT_APP_GITHUB_DOMAIN=https://github.com/
19+
REACT_APP_GITHUB_MAIN=https://github.com/ani-team/github-client
20+
REACT_APP_GITHUB_FEEDBACK=https://github.com/ani-team/github-client/issues/new
21+
22+
# Используется для кодогенерации, Apollo Client плагина
23+
REACT_APP_ACCESS_TOKEN=9221b1b5ba4b0de72eed80b09602ed7b4c73c222

src/.deploy/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1+
/**
2+
* Модуль для облегченной реализации для temp-стендов на firebase (guest mode)
3+
* @remark
4+
* - Связано с тем, что сложно реализовать полноценную авторизацию для temp-стендов, разворачиваемых
5+
* Во время pull-requests
6+
* - Модуль нужен только для разработки - во время основной работы приложения же - не используется
7+
*/
8+
19
export * from "./temp-stand";

src/.deploy/temp-stand.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const isTempStand = () => tempStandRegex.test(window.location.host);
77
const STAND_URL_ENV = "REACT_APP_DEV_STORAGE_URL";
88
const devStorageUrl = process.env[STAND_URL_ENV];
99

10+
/**
11+
* Инициализация гостевого режима с псевдо-авторизацией
12+
*/
1013
export const loadLocalStorageFromDevIfNeeded = async () => {
1114
if (!isTempStand() || !devStorageUrl) {
1215
if(!devStorageUrl) {

src/app/error-handling/error-catcher.tsx

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,20 @@
11
import React, { useEffect, useState, ReactNode } from "react";
2-
import { ServerError, useApolloClient } from "@apollo/client";
32
import { onError } from "@apollo/client/link/error";
4-
import { GraphQLError } from "graphql";
3+
import { useApolloClient } from "@apollo/client";
54
import { useLocation } from "react-router";
65
import { AppError } from "models";
7-
import { ErrorDefinitions } from "./error-definitions";
8-
9-
const isGithubError = (error: any): error is { type: string } => {
10-
return typeof error.type === "string";
11-
};
12-
13-
const isServerError = (error: any): error is ServerError => {
14-
return typeof error.statusCode === "number";
15-
};
16-
17-
function mapError(error: GraphQLError | Error | ServerError | undefined): AppError | null {
18-
if (!error) return null;
19-
if (isGithubError(error)) {
20-
// FIXME: handle 403 and 500 errors as well w/o side effects
21-
if (error.type === "NOT_FOUND") {
22-
return ErrorDefinitions[error.type];
23-
}
24-
}
25-
if (isServerError(error)) {
26-
if (error.statusCode === 401) return ErrorDefinitions.UNAUTHORIZED;
27-
}
28-
// TODO: handle network errors and whatever can be broken
29-
return null;
30-
}
6+
import { mapError } from "./helpers";
317

328
type Props = PropsWithChildren<{
9+
/** Отрисовщик-обработчик ошибки */
3310
handler: (props: { error: AppError }) => ReactNode;
3411
}>;
3512

36-
const ErrorCatcher = ({ handler, children }: Props) => {
13+
/**
14+
* @hook Логика обработки и хранения ошибок
15+
*/
16+
const useAppError = () => {
3717
const apolloClient = useApolloClient();
38-
const location = useLocation();
3918
const [error, setError] = useState<AppError | null>(null);
4019

4120
useEffect(() => {
@@ -46,7 +25,18 @@ const ErrorCatcher = ({ handler, children }: Props) => {
4625
// eslint-disable-next-line react-hooks/exhaustive-deps
4726
}, []);
4827

49-
useEffect(() => setError(null), [location]);
28+
return { error, setError };
29+
};
30+
31+
/**
32+
* Обертка для обработки ошибок
33+
* FIXME: add ErrorBoundaries
34+
*/
35+
const ErrorCatcher = ({ handler, children }: Props) => {
36+
const location = useLocation();
37+
const { error, setError } = useAppError();
38+
39+
useEffect(() => setError(null), [location, setError]);
5040

5141
if (error) {
5242
return <>{handler({ error })}</>;

src/app/error-handling/error-definitions.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { AppError } from "models";
22

3+
/**
4+
* Обрабатываемые ошибки приложения
5+
*/
36
export const ErrorDefinitions: Record<string, AppError> = {
47
UNAUTHORIZED: {
58
code: 401,

src/app/error-handling/helpers.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { ServerError } from "@apollo/client";
2+
import { GraphQLError } from "graphql";
3+
import { AppError } from "models";
4+
import { ErrorDefinitions } from "./error-definitions";
5+
6+
/**
7+
* Проверка: является ли ошибка GitHub Error
8+
*/
9+
const isGithubError = (error: any): error is { type: string } => {
10+
return typeof error.type === "string";
11+
};
12+
13+
/**
14+
* Проверка: является ли ошибка серверной
15+
*/
16+
const isServerError = (error: any): error is ServerError => {
17+
return typeof error.statusCode === "number";
18+
};
19+
20+
/**
21+
* Соответствие полученной ошибки с прописанными и обрабатываемыми на уровне приложения
22+
* @see ErrorDefinitions
23+
*/
24+
export function mapError(error?: GraphQLError | Error | ServerError): AppError | null {
25+
if (!error) return null;
26+
if (isGithubError(error)) {
27+
// FIXME: handle 403 and 500 errors as well w/o side effects
28+
if (error.type === "NOT_FOUND") {
29+
return ErrorDefinitions[error.type];
30+
}
31+
}
32+
if (isServerError(error)) {
33+
if (error.statusCode === 401) return ErrorDefinitions.UNAUTHORIZED;
34+
}
35+
// TODO: handle network errors and whatever can be broken
36+
return null;
37+
}

src/app/header/index.tsx

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import React from "react";
1+
import React, { KeyboardEventHandler } from "react";
22
import { Layout, Input } from "antd";
33
import { Link, useHistory, useLocation } from "react-router-dom";
44
import { StringParam, useQueryParams } from "use-query-params";
55
import * as qs from "query-string";
6+
import { GITHUB_MAIN, GITHUB_FEEDBACK } from "shared/get-env";
67
import { Auth } from "features";
78
import { ReactComponent as IcLogo } from "./logo.svg";
89
import "./index.scss";
910

10-
const FEEDBACK_URL = "https://github.com/ani-team/github-client/issues/new";
11-
const GITHUB_URL = "https://github.com/ani-team/github-client";
11+
// FIXME: get from `pages`?
12+
const SEARCH_URL = "/search";
1213

13-
const Header = () => {
14-
const { isAuth } = Auth.useAuth();
14+
/**
15+
* @hook Логика обработки инпута поиска
16+
*/
17+
const useSearchInput = () => {
1518
// !!! FIXME: limit scope of query-params literals
1619
const [query] = useQueryParams({
1720
q: StringParam,
@@ -22,6 +25,32 @@ const Header = () => {
2225
const location = useLocation();
2326
const history = useHistory();
2427

28+
/**
29+
* Обработка инпута поиска
30+
*/
31+
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = ({ key, currentTarget }) => {
32+
if (key === "Enter" && currentTarget.value) {
33+
const q = currentTarget.value;
34+
history.push(`${SEARCH_URL}?${qs.stringify({ ...query, q })}`);
35+
}
36+
};
37+
/**
38+
* Поисковой запрос
39+
* @remark Если не страница поиска - обнуляем инпут
40+
*/
41+
const searchValue = location.pathname === SEARCH_URL ? query.q ?? "" : "";
42+
43+
return { handleKeyDown, searchValue };
44+
};
45+
46+
/**
47+
* Хедер приложения
48+
* @remark Содержит поисковой инпут с базовой логикой
49+
*/
50+
const Header = () => {
51+
const { isAuth } = Auth.useAuth();
52+
const { handleKeyDown, searchValue } = useSearchInput();
53+
2554
return (
2655
<Layout.Header className="header">
2756
<div className="nav flex flex-grow items-center">
@@ -33,25 +62,14 @@ const Header = () => {
3362
<Input
3463
className="header__search"
3564
placeholder="Search..."
36-
defaultValue={location.pathname === "/search" ? query.q ?? "" : ""}
37-
onKeyDown={({ key, currentTarget }) => {
38-
if (key === "Enter" && currentTarget.value) {
39-
history.push(
40-
`/search?${qs.stringify({
41-
q: currentTarget.value,
42-
type: query.type,
43-
s: query.s,
44-
o: query.o,
45-
})}`,
46-
);
47-
}
48-
}}
65+
defaultValue={searchValue}
66+
onKeyDown={handleKeyDown}
4967
/>
5068
)}
51-
<a className="m-4 text-gray-600" href={GITHUB_URL}>
69+
<a className="m-4 text-gray-600" href={GITHUB_MAIN}>
5270
GitHub
5371
</a>
54-
<a className="m-4 text-gray-600" href={FEEDBACK_URL}>
72+
<a className="m-4 text-gray-600" href={GITHUB_FEEDBACK}>
5573
Feedback
5674
</a>
5775
</div>

src/app/hocs/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import withApollo from "./with-apollo";
22
import withRouter from "./with-router";
33

4-
// Потом какой-нибудь `compose` метод заинсталлим откуда-нить и покрасивше будет
5-
export const withHocs = (component: () => JSX.Element) => withRouter(withApollo(component));
4+
/**
5+
* @hoc Инициализирующая логика приложения
6+
* FIXME: Потом какой-нибудь `compose` метод заинсталлим откуда-нить и покрасивше будет
7+
*/
8+
export const withHocs = (component: Component) => withRouter(withApollo(component));

src/app/hocs/with-apollo.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@ import { setContext } from "@apollo/client/link/context";
44
import { API_URL } from "shared/get-env";
55
import { Auth } from "features";
66

7-
// TODO: Уточнить, нужно ли дополнительно задавать контекст
8-
7+
/**
8+
* Инициализация API.baseUrl
9+
*/
910
const httpLink = createHttpLink({
1011
uri: API_URL,
1112
});
1213

14+
/**
15+
* Логика авторизации
16+
* FIXME: вынести в features/auth?
17+
*/
1318
const authLink = setContext((_, { headers }) => {
1419
// get the authentication token from local storage if it exists
1520
const token = Auth.getToken();
@@ -23,6 +28,7 @@ const authLink = setContext((_, { headers }) => {
2328
});
2429

2530
/**
31+
* Инициализация инстанса клиента
2632
* @see https://www.apollographql.com/docs/react/networking/authentication/
2733
*/
2834
const client = new ApolloClient({
@@ -32,9 +38,9 @@ const client = new ApolloClient({
3238
});
3339

3440
/**
35-
* Обертка для подключения и работы с API
41+
* @hoc Инициализация подключения apollo для работы с API
3642
*/
37-
const withApollo = (component: () => JSX.Element) => () => (
43+
const withApollo = (component: Component) => () => (
3844
<ApolloProvider client={client}>{component()}</ApolloProvider>
3945
);
4046

src/app/hocs/with-router.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { BrowserRouter, Route } from "react-router-dom";
44
import { QueryParamProvider } from "use-query-params";
55

66
/**
7-
* Инициализация роутера с провайдером для работы с get-параметрами
7+
* @hoc Инициализация роутера с провайдером для работы с get-параметрами
88
*/
9-
const withRouter = (component: () => JSX.Element) => () => (
9+
const withRouter = (component: Component) => () => (
1010
<BrowserRouter>
1111
<Suspense fallback={<Spin delay={300} className="overlay" size="large" />}>
1212
<QueryParamProvider ReactRouterRoute={Route}>{component()}</QueryParamProvider>

0 commit comments

Comments
 (0)