diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..08f9fcb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+
+# dependencies
+/node_modules
+
+# testing
+/coverage
+
+# build
+/build
+
+# envs
+/.env
+
+# Misc
+.DS_Store
+
+# Editors and IDEs
+.idea
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# Logs
+npm-debug.log*
+npm-error.log*
+yarn-debug.log*
+yarn-error.log*
+
+# awesome-typescript-loader + babel-loader cache
+.awcache
+
+
+# videoshots
+videoshot-with-error/
+
+.eslintinfo
+.stylelintinfo
+.todo
+todo
\ No newline at end of file
diff --git a/src/components/App.tsx b/src/components/App.tsx
new file mode 100644
index 0000000..6f5dd76
--- /dev/null
+++ b/src/components/App.tsx
@@ -0,0 +1,183 @@
+import { useCallback, useState } from "react";
+
+import { Api } from "../utils/Api";
+
+/*
+ Легенда:
+ Необходимо отобразить список ключей (произвольные строки), запрашиваемый с бэкенда.
+ При нажатии кнопки "Сгенерировать ключ" необходимо сгенерировать новый ключ (апи запрос) и отобразить его в списке.
+ Каждый ключ можно использовать. В таком случае он должен изменить свое визуальное отображение и увеличить счетчик использованных ключей.
+ Каждый ключ можно удалить. При удалении использованного ключа, счетчик использованных ключей уменьшается (счетчику важны только неудаленные использованные ключи)
+
+ Для поддержания актуального списка ключей необходимо обновлять список каждые 30 секунд. При получении от апи новых ключей считать их неиспользованными
+ */
+
+export function App() {
+ const [keys, setKeys] = useState(null);
+ const [isKeysRequested, setIsKeysRequested] = useState(false);
+
+ const [isLoading, setLoadingState] = useState(false);
+ const [countUsedKeys, setCountUsedKeys] = useState(0);
+
+ const toggleLoading = useCallback(() => {
+ setLoadingState(!isLoading);
+ }, [isLoading]);
+
+ const incrementUsedKeys = () => {
+ setCountUsedKeys((prevValue) => prevValue++);
+ }
+
+ const decrementUsedKeys = useCallback(() => {
+ setCountUsedKeys((prevValue) => prevValue--);
+ }, []);
+
+ const addKey = useCallback(async () => {
+ toggleLoading();
+
+ const key = await Api.generateKey();
+ setKeys((prevKeys) => prevKeys.push(key));
+
+ toggleLoading();
+ }, [toggleLoading]);
+
+ const removeKey = useCallback((value) => {
+ setKeys((prevKeys) => prevKeys.filter((key) => key !== value));
+ }, []);
+
+ if (!isKeysRequested) {
+ setIsKeysRequested(true);
+ Api.loadKeys().then((response) => {
+ setKeys(response);
+ setInterval(() => {
+ Api.loadKeys().then((response) => {
+ setKeys(response);
+ })
+ }, 30000);
+ }).catch(e => {
+ setIsKeysRequested(false);
+ })
+ }
+
+ return (
+ Всего ключей: {keys.length}
+ Использовано текущих ключей: {countUsedKeys}
+