add first services
This commit is contained in:
32
.eslintrc.cjs
Normal file
32
.eslintrc.cjs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2021: true, jest: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:jsx-a11y/recommended',
|
||||||
|
'prettier', // Важно: prettier должен быть последним
|
||||||
|
],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
project: './tsconfig.json', // Для правил, требующих информацию о типах
|
||||||
|
},
|
||||||
|
plugins: ['react', '@typescript-eslint', 'jsx-a11y'],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect', // Автоматически определять версию React
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Отключаем правило, которое не нужно с новым JSX-трансформером
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
// Мы используем TypeScript, поэтому prop-types не нужны
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
// Пример кастомного правила: требовать явное указание возвращаемого типа функции
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'warn',
|
||||||
|
},
|
||||||
|
};
|
||||||
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"singleQuote": true,
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
||||||
23
jest.config.ts
Normal file
23
jest.config.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export default {
|
||||||
|
// Указываем пресет для работы с TypeScript
|
||||||
|
preset: 'ts-jest',
|
||||||
|
|
||||||
|
// Указываем тестовую среду, эмулирующую DOM
|
||||||
|
testEnvironment: 'jest-environment-jsdom',
|
||||||
|
|
||||||
|
// Путь к файлу с глобальными настройками для тестов
|
||||||
|
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||||
|
|
||||||
|
// Трансформер для файлов TypeScript
|
||||||
|
transform: {
|
||||||
|
'^.+\\.tsx?$': 'ts-jest',
|
||||||
|
},
|
||||||
|
|
||||||
|
// Маппинг для обработки импортов, которые Jest не понимает
|
||||||
|
moduleNameMapper: {
|
||||||
|
// Мокируем импорты стилей
|
||||||
|
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
|
||||||
|
// Настраиваем алиас '@' так же, как в Vite и tsconfig
|
||||||
|
'^@/(.*)$': '<rootDir>/src/$1',
|
||||||
|
},
|
||||||
|
};
|
||||||
3
jest.setup.ts
Normal file
3
jest.setup.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// Расширяет ожидания Jest с помощью удобных матчеров для DOM
|
||||||
|
// Например, позволяет писать expect(element).toBeInTheDocument()
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
10076
package-lock.json
generated
Normal file
10076
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
55
package.json
Normal file
55
package.json
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
{
|
||||||
|
"name": "task-tracker-for-students",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Проект для примеры реализации приложения Task Tracker на основе макетов https://www.figma.com/design/oHNPqHqhNtil2xuWBjZfYW/Task-tracker",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"lint:fix": "eslint . --ext ts,tsx --fix",
|
||||||
|
"format": "prettier --write \"**/*.{ts,tsx,json,css,md}\"",
|
||||||
|
"test": "jest"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@reatom/core": "3.10.1",
|
||||||
|
"@reatom/npm-react": "3.10.6",
|
||||||
|
"axios": "1.11.0",
|
||||||
|
"date-fns": "4.1.0",
|
||||||
|
"formik": "2.4.6",
|
||||||
|
"keycloak-js": "26.2.0",
|
||||||
|
"react": "19.1.1",
|
||||||
|
"react-dom": "19.1.1",
|
||||||
|
"react-jss": "10.10.0",
|
||||||
|
"react-router-dom": "7.7.1",
|
||||||
|
"uuid": "11.1.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@testing-library/jest-dom": "6.6.4",
|
||||||
|
"@testing-library/react": "16.3.0",
|
||||||
|
"@types/jest": "30.0.0",
|
||||||
|
"@types/react": "19.1.9",
|
||||||
|
"@types/react-dom": "19.1.7",
|
||||||
|
"@types/uuid": "10.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||||
|
"@typescript-eslint/parser": "8.38.0",
|
||||||
|
"@vitejs/plugin-react": "4.7.0",
|
||||||
|
"eslint": "9.32.0",
|
||||||
|
"eslint-config-prettier": "10.1.8",
|
||||||
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||||
|
"eslint-plugin-react-hooks": "5.2.0",
|
||||||
|
"eslint-plugin-react-refresh": "0.4.20",
|
||||||
|
"identity-obj-proxy": "3.0.0",
|
||||||
|
"jest": "30.0.5",
|
||||||
|
"jest-environment-jsdom": "30.0.5",
|
||||||
|
"prettier": "3.6.2",
|
||||||
|
"ts-jest": "29.4.0",
|
||||||
|
"ts-node": "10.9.2",
|
||||||
|
"typescript": "5.8.3",
|
||||||
|
"vite": "7.0.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/app/components/App/App.tsx
Normal file
53
src/app/components/App/App.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { BrowserRouter, Route, Routes } from 'react-router';
|
||||||
|
import { TasksPage } from '../pages/TasksPage';
|
||||||
|
import { BottomMenu } from '../BottomMenu/BottomMenu';
|
||||||
|
import { CalendarPage } from '../pages/CalendarPage';
|
||||||
|
import { BoardPage } from '../pages/BoardPage';
|
||||||
|
import { reatomContext as ReatomContext } from '@reatom/npm-react';
|
||||||
|
import { ctx } from '../../../core/services/ReatomContext';
|
||||||
|
import { AuthProvider } from '../../../core/services/AuthService';
|
||||||
|
import { AuthRoute } from '../../../core/components/AuthRoute';
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
return (
|
||||||
|
<ReatomContext.Provider value={ctx}>
|
||||||
|
<AuthProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<div>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<TasksPage />} />
|
||||||
|
<Route
|
||||||
|
path="/calendar"
|
||||||
|
element={
|
||||||
|
<AuthRoute>
|
||||||
|
<CalendarPage />
|
||||||
|
</AuthRoute>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/board" element={<BoardPage />} />
|
||||||
|
</Routes>
|
||||||
|
<BottomMenu
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
title: 'Tasks',
|
||||||
|
image: '',
|
||||||
|
route: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Calendar',
|
||||||
|
image: '',
|
||||||
|
route: '/calendar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Board',
|
||||||
|
image: '',
|
||||||
|
route: '/board',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</BrowserRouter>
|
||||||
|
</AuthProvider>
|
||||||
|
</ReatomContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
20
src/app/components/BottomMenu/BottomMenu.tsx
Normal file
20
src/app/components/BottomMenu/BottomMenu.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
type BottomMenuProps = {
|
||||||
|
items: MenuItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BottomMenu: FC<BottomMenuProps> = ({ items }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{items.map(({ title, image, route }) => {
|
||||||
|
return (
|
||||||
|
<Link key={route} to={route}>
|
||||||
|
{title}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
5
src/app/components/BottomMenu/types.ts
Normal file
5
src/app/components/BottomMenu/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
type MenuItem = {
|
||||||
|
image: string;
|
||||||
|
title: string;
|
||||||
|
route: string;
|
||||||
|
};
|
||||||
3
src/app/components/pages/BoardPage.tsx
Normal file
3
src/app/components/pages/BoardPage.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const BoardPage = () => {
|
||||||
|
return <div>Board page</div>;
|
||||||
|
};
|
||||||
3
src/app/components/pages/CalendarPage.tsx
Normal file
3
src/app/components/pages/CalendarPage.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const CalendarPage = () => {
|
||||||
|
return <div>Calendar page</div>;
|
||||||
|
};
|
||||||
36
src/app/components/pages/TasksPage.tsx
Normal file
36
src/app/components/pages/TasksPage.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { useAtom } from '@reatom/npm-react';
|
||||||
|
import { removeTask, tasksAtom } from '../../services/TasksService';
|
||||||
|
import { authAtom, login } from '../../../core/services/AuthService';
|
||||||
|
|
||||||
|
export const TasksPage = () => {
|
||||||
|
const [tasks] = useAtom(tasksAtom);
|
||||||
|
const [auth] = useAtom(authAtom);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
{auth.isAuth ? (
|
||||||
|
'Привет пользователь'
|
||||||
|
) : (
|
||||||
|
<button type="button" onClick={login}>
|
||||||
|
Войти
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{tasks.map(({ id, title, description }) => {
|
||||||
|
const handleRemoveTask = () => {
|
||||||
|
removeTask(id);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div key={id}>
|
||||||
|
<div>Title: {title}</div>
|
||||||
|
<div>Description: {description}</div>
|
||||||
|
<button type="button" onClick={handleRemoveTask}>
|
||||||
|
remove
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
29
src/app/services/TasksService.ts
Normal file
29
src/app/services/TasksService.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { action, atom } from '@reatom/core';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
import { ctx } from '../../core/services/ReatomContext';
|
||||||
|
|
||||||
|
type Uuid = string;
|
||||||
|
|
||||||
|
type Task = {
|
||||||
|
id: Uuid;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const tasksAtom = atom<Task[]>(
|
||||||
|
[
|
||||||
|
{ id: v4(), title: 'Clean bathroom', description: 'Clean all corners' },
|
||||||
|
{ id: v4(), title: 'Buy a car', description: 'Go to shop and buy a car' },
|
||||||
|
],
|
||||||
|
'tasks',
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeTaskAction = action((ctx, removeTaskId: Uuid) => {
|
||||||
|
const taskList = ctx.get(tasksAtom);
|
||||||
|
|
||||||
|
const filteredTaskList = taskList.filter((t) => t.id !== removeTaskId);
|
||||||
|
|
||||||
|
tasksAtom(ctx, filteredTaskList);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const removeTask = (removeTaskId: Uuid) => removeTaskAction(ctx, removeTaskId);
|
||||||
13
src/core/components/AuthRoute.tsx
Normal file
13
src/core/components/AuthRoute.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { FC, PropsWithChildren, ReactNode, useEffect } from 'react';
|
||||||
|
import { authAtom } from '../services/AuthService';
|
||||||
|
import {useAtom} from '@reatom/npm-react';
|
||||||
|
|
||||||
|
export const AuthRoute: FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const [auth] = useAtom(authAtom);
|
||||||
|
|
||||||
|
if (!auth.isAuth) {
|
||||||
|
return <div>you need auth</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
};
|
||||||
67
src/core/services/AuthService.tsx
Normal file
67
src/core/services/AuthService.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { action, atom } from '@reatom/core';
|
||||||
|
import Keycloak from 'keycloak-js';
|
||||||
|
import { createContext, FC, PropsWithChildren, useContext, useEffect, useRef } from 'react';
|
||||||
|
import { ctx } from './ReatomContext';
|
||||||
|
|
||||||
|
const authInstance = new Keycloak({
|
||||||
|
url: 'https://auth.vigdorov.ru',
|
||||||
|
realm: 'dev-apps',
|
||||||
|
clientId: 'test-localapp',
|
||||||
|
});
|
||||||
|
|
||||||
|
const AuthContext = createContext<typeof authInstance | undefined>(undefined);
|
||||||
|
|
||||||
|
type AuthData =
|
||||||
|
| {
|
||||||
|
isAuth: false;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
isAuth: true;
|
||||||
|
userName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authAtom = atom<AuthData>(
|
||||||
|
{
|
||||||
|
isAuth: false,
|
||||||
|
},
|
||||||
|
'auth',
|
||||||
|
);
|
||||||
|
|
||||||
|
const changeAuthAction = action((ctx, authData: AuthData) => {
|
||||||
|
authAtom(ctx, authData);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const login = () => authInstance.login();
|
||||||
|
|
||||||
|
export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
|
const isInit = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isInit.current) {
|
||||||
|
isInit.current = true;
|
||||||
|
|
||||||
|
authInstance
|
||||||
|
.init({ onLoad: 'check-sso' })
|
||||||
|
.then((authenticated) => {
|
||||||
|
const userName: string =
|
||||||
|
authInstance.tokenParsed?.given_name || authInstance.tokenParsed?.preferred_username;
|
||||||
|
|
||||||
|
const authData = authenticated
|
||||||
|
? {
|
||||||
|
isAuth: authenticated,
|
||||||
|
userName,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
isAuth: authenticated,
|
||||||
|
};
|
||||||
|
changeAuthAction(ctx, authData);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Ошибка инициализации Keycloak', error);
|
||||||
|
changeAuthAction(ctx, { isAuth: false });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <AuthContext.Provider value={authInstance}>{children}</AuthContext.Provider>;
|
||||||
|
};
|
||||||
20
src/core/services/InitApp.tsx
Normal file
20
src/core/services/InitApp.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {useAtom} from '@reatom/npm-react';
|
||||||
|
import {createContext, FC, PropsWithChildren, useEffect, useRef} from 'react';
|
||||||
|
import {authAtom} from './AuthService';
|
||||||
|
|
||||||
|
type InitData = {
|
||||||
|
isInit: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const InitContext = createContext<InitData>({isInit: false});
|
||||||
|
|
||||||
|
export const InitProvider: FC<PropsWithChildren> = ({children}) => {
|
||||||
|
const isInit = useRef(false);
|
||||||
|
|
||||||
|
const [authData] = useAtom(authAtom);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
}, [authData])
|
||||||
|
|
||||||
|
}
|
||||||
3
src/core/services/ReatomContext.ts
Normal file
3
src/core/services/ReatomContext.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import {createCtx} from '@reatom/core';
|
||||||
|
|
||||||
|
export const ctx = createCtx();
|
||||||
138
src/core/services/StorageService.ts
Normal file
138
src/core/services/StorageService.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const BASE_URL = 'https://simple-storage.vigdorov.ru';
|
||||||
|
|
||||||
|
const STORAGE_NAME = 'task-tracker-app-for-students' as const;
|
||||||
|
|
||||||
|
type AuthRequest = {
|
||||||
|
login: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AuthResponse = string;
|
||||||
|
|
||||||
|
type Uuid = string;
|
||||||
|
|
||||||
|
type Task = {
|
||||||
|
id: Uuid;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StorageForStudentsListItem = {
|
||||||
|
user: string;
|
||||||
|
storageName: typeof STORAGE_NAME;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StorageForStudents = {
|
||||||
|
data: Task[];
|
||||||
|
user: string;
|
||||||
|
storageName: typeof STORAGE_NAME;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StorageCreateRequest = {
|
||||||
|
data: any;
|
||||||
|
storageName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AnyStorageListItem = {
|
||||||
|
user: string;
|
||||||
|
storageName: string;
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateStorageRequest = {
|
||||||
|
data: Task[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StorageListItem = StorageForStudentsListItem | AnyStorageListItem;
|
||||||
|
|
||||||
|
type StorageListResponse = StorageListItem[];
|
||||||
|
|
||||||
|
export class StorageService {
|
||||||
|
userName: string;
|
||||||
|
token: string;
|
||||||
|
id: string | undefined;
|
||||||
|
|
||||||
|
constructor(userName: string) {
|
||||||
|
this.userName = userName;
|
||||||
|
this.token = '';
|
||||||
|
|
||||||
|
axios
|
||||||
|
.post<void, AuthResponse, AuthRequest>(`${BASE_URL}/auth`, {
|
||||||
|
login: userName,
|
||||||
|
})
|
||||||
|
.then((token) => {
|
||||||
|
this.token = token;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
this.token = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStorage(): Promise<Task[]> {
|
||||||
|
return axios
|
||||||
|
.get<void, StorageListResponse>(`${BASE_URL}/storages`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.token,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((storageListResponse) => {
|
||||||
|
const findStorage = storageListResponse.find(
|
||||||
|
(storage): storage is StorageForStudentsListItem => storage.storageName === STORAGE_NAME,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (findStorage) {
|
||||||
|
return findStorage.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return axios
|
||||||
|
.post<void, StorageForStudentsListItem, StorageCreateRequest>(
|
||||||
|
`${BASE_URL}/storages`,
|
||||||
|
{
|
||||||
|
data: [],
|
||||||
|
storageName: STORAGE_NAME,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: this.token,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.then((storageForStudentsListItem) => {
|
||||||
|
return storageForStudentsListItem.id;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then((id) => {
|
||||||
|
this.id = id;
|
||||||
|
|
||||||
|
return axios
|
||||||
|
.get<void, StorageForStudents>(`${BASE_URL}/storages/${id}`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.token,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((storageForStudents) => storageForStudents.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setStorage(taskList: Task[]): Promise<void> {
|
||||||
|
if (!this.id) {
|
||||||
|
return Promise.reject('Id отсутствует');
|
||||||
|
}
|
||||||
|
|
||||||
|
return axios.put<void, void, UpdateStorageRequest>(
|
||||||
|
`${BASE_URL}/storages/${this.id}`,
|
||||||
|
{
|
||||||
|
data: taskList,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: this.token,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/index.html
Normal file
12
src/index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="./init.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
9
src/init.tsx
Normal file
9
src/init.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import {App} from './app/components/App/App';
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
|
"allowJs": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Path Aliases */
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
10
tsconfig.node.json
Normal file
10
tsconfig.node.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts", "jest.config.ts"]
|
||||||
|
}
|
||||||
17
vite.config.ts
Normal file
17
vite.config.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
root: 'src',
|
||||||
|
build: {
|
||||||
|
outDir: '../build',
|
||||||
|
emptyOutDir: true,
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
// Порт для сервера разработки
|
||||||
|
port: 3050,
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user