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}

+
+ + {!keys.length &&
Список ключей пуст
} + + {keys.length && ( +
+ {keys.map((key) => ( + + ))} +
+ )} + +
+
+
+ ); +}; + +export function Button({ + size, + label, + color, + onClick, + disabled, + isLoading, + }) { + const className = useMemo( + () => cn([size && `size_${size}`, color && `color_${color}`]), + [color, size] + ); + + return ( + + ); +}; + +export function Key({ + value, + removeKey, + incrementUsedKeys, + decrementUsedKeys, + }) { + const [isUsed, setUsedState] = useState(false); + const [isLoading, setLoadingState] = useState(false); + + const toggleLoading = useCallback(() => { + setLoadingState(!isLoading); + }, [isLoading]); + + const applyKey = useCallback( + async (value) => { + if (isUsed) return; + + toggleLoading(); + await Api.addUsedKey(value); + toggleLoading(); + + setUsedState(true); + incrementUsedKeys(); + }, + [incrementUsedKeys, isUsed, toggleLoading] + ); + + useEffect(async () => { + return async () => { + if (!isUsed) return; + + decrementUsedKeys(); + await Api.removeUsedKey(value); + }; + }, [decrementUsedKeys, isUsed, value]); + + const valueClassNames = useMemo( + () => cn(["key__value", isUsed && "key__value_used"]), + [isUsed] + ); + + const buttonLabel = isUsed ? "Использован" : "Использовать"; + + return ( +
+
{value}
+ +
+
+
+ ); +};