live lesson 21-01-24

This commit is contained in:
2024-01-21 19:36:50 +01:00
parent a659c7e282
commit 1e6df4203f
13 changed files with 467 additions and 72 deletions

View File

@@ -18,11 +18,13 @@
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-radio-group": "^1.1.3",
"@reduxjs/toolkit": "^2.0.1",
"@storybook/theming": "^7.2.1",
"clsx": "^2.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.2",
"react-redux": "^9.1.0",
"react-router-dom": "^6.14.2",
"react-toastify": "^9.1.3",
"remeda": "^1.24.0",
@@ -55,7 +57,7 @@
"sass": "^1.64.1",
"storybook": "^7.2.1",
"stylelint": "^15.10.2",
"typescript": "^5.0.2",
"typescript": "^5.3.3",
"vite": "^4.4.5"
}
}

197
pnpm-lock.yaml generated
View File

@@ -20,6 +20,9 @@ dependencies:
'@radix-ui/react-radio-group':
specifier: ^1.1.3
version: 1.1.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
'@reduxjs/toolkit':
specifier: ^2.0.1
version: 2.0.1(react-redux@9.1.0)(react@18.2.0)
'@storybook/theming':
specifier: ^7.2.1
version: 7.2.1(react-dom@18.2.0)(react@18.2.0)
@@ -35,6 +38,9 @@ dependencies:
react-hook-form:
specifier: ^7.45.2
version: 7.45.2(react@18.2.0)
react-redux:
specifier: ^9.1.0
version: 9.1.0(@types/react@18.2.15)(react@18.2.0)(redux@5.0.1)
react-router-dom:
specifier: ^6.14.2
version: 6.14.2(react-dom@18.2.0)(react@18.2.0)
@@ -57,7 +63,7 @@ devDependencies:
version: 4.3.1(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
'@it-incubator/eslint-config':
specifier: ^0.1.3
version: 0.1.3(prettier@3.0.0)(typescript@5.0.2)
version: 0.1.3(prettier@3.0.0)(typescript@5.3.3)
'@it-incubator/prettier-config':
specifier: ^0.1.2
version: 0.1.2
@@ -81,10 +87,10 @@ devDependencies:
version: 7.2.1(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0)
'@storybook/react':
specifier: ^7.2.1
version: 7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.2)
version: 7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
'@storybook/react-vite':
specifier: ^7.2.1
version: 7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.2)(vite@4.4.5)
version: 7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(vite@4.4.5)
'@storybook/testing-library':
specifier: ^0.2.0
version: 0.2.0
@@ -99,10 +105,10 @@ devDependencies:
version: 18.2.7
'@typescript-eslint/eslint-plugin':
specifier: ^6.0.0
version: 6.0.0(@typescript-eslint/parser@6.0.0)(eslint@8.45.0)(typescript@5.0.2)
version: 6.0.0(@typescript-eslint/parser@6.0.0)(eslint@8.45.0)(typescript@5.3.3)
'@typescript-eslint/parser':
specifier: ^6.0.0
version: 6.0.0(eslint@8.45.0)(typescript@5.0.2)
version: 6.0.0(eslint@8.45.0)(typescript@5.3.3)
'@vitejs/plugin-react':
specifier: ^4.0.3
version: 4.0.3(vite@4.4.5)
@@ -117,7 +123,7 @@ devDependencies:
version: 0.4.3(eslint@8.45.0)
eslint-plugin-storybook:
specifier: ^0.6.13
version: 0.6.13(eslint@8.45.0)(typescript@5.0.2)
version: 0.6.13(eslint@8.45.0)(typescript@5.3.3)
sass:
specifier: ^1.64.1
version: 1.64.1
@@ -128,8 +134,8 @@ devDependencies:
specifier: ^15.10.2
version: 15.10.2
typescript:
specifier: ^5.0.2
version: 5.0.2
specifier: ^5.3.3
version: 5.3.3
vite:
specifier: ^4.4.5
version: 4.4.5(@types/node@20.4.5)(sass@1.64.1)
@@ -1939,11 +1945,11 @@ packages:
engines: {node: '>=8'}
dev: true
/@it-incubator/eslint-config@0.1.3(prettier@3.0.0)(typescript@5.0.2):
/@it-incubator/eslint-config@0.1.3(prettier@3.0.0)(typescript@5.3.3):
resolution: {integrity: sha512-YABg8TFU/v514BWrHpFuJmqMLKF6wA+bHJJm1ZBhTAsm57GGcwGsz4xHrByoFF8Q/TbmzxF5DZ4WoKJPqupkmQ==}
dependencies:
'@typescript-eslint/eslint-plugin': 6.0.0(@typescript-eslint/parser@6.0.0)(eslint@8.45.0)(typescript@5.0.2)
'@typescript-eslint/parser': 6.0.0(eslint@8.45.0)(typescript@5.0.2)
'@typescript-eslint/eslint-plugin': 6.0.0(@typescript-eslint/parser@6.0.0)(eslint@8.45.0)(typescript@5.3.3)
'@typescript-eslint/parser': 6.0.0(eslint@8.45.0)(typescript@5.3.3)
eslint: 8.45.0
eslint-config-prettier: 8.8.0(eslint@8.45.0)
eslint-import-resolver-node: 0.3.7
@@ -2029,7 +2035,7 @@ packages:
chalk: 4.1.2
dev: true
/@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.0.2)(vite@4.4.5):
/@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.3.3)(vite@4.4.5):
resolution: {integrity: sha512-ou4ZJSXMMWHqGS4g8uNRbC5TiTWxAgQZiVucoUrOCWuPrTbkpJbmVyIi9jU72SBry7gQtuMEDp4YR8EEXAg7VQ==}
peerDependencies:
typescript: '>= 4.3.x'
@@ -2041,8 +2047,8 @@ packages:
glob: 7.2.3
glob-promise: 4.2.2(glob@7.2.3)
magic-string: 0.27.0
react-docgen-typescript: 2.2.2(typescript@5.0.2)
typescript: 5.0.2
react-docgen-typescript: 2.2.2(typescript@5.3.3)
typescript: 5.3.3
vite: 4.4.5(@types/node@20.4.5)(sass@1.64.1)
dev: true
@@ -2672,6 +2678,25 @@ packages:
dependencies:
'@babel/runtime': 7.22.6
/@reduxjs/toolkit@2.0.1(react-redux@9.1.0)(react@18.2.0):
resolution: {integrity: sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA==}
peerDependencies:
react: ^16.9.0 || ^17.0.0 || ^18
react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
peerDependenciesMeta:
react:
optional: true
react-redux:
optional: true
dependencies:
immer: 10.0.3
react: 18.2.0
react-redux: 9.1.0(@types/react@18.2.15)(react@18.2.0)(redux@5.0.1)
redux: 5.0.1
redux-thunk: 3.1.0(redux@5.0.1)
reselect: 5.1.0
dev: false
/@remix-run/router@1.7.2:
resolution: {integrity: sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A==}
engines: {node: '>=14'}
@@ -3113,7 +3138,7 @@ packages:
- supports-color
dev: true
/@storybook/builder-vite@7.2.1(typescript@5.0.2)(vite@4.4.5):
/@storybook/builder-vite@7.2.1(typescript@5.3.3)(vite@4.4.5):
resolution: {integrity: sha512-D/RNcH6WAxMAMmC3w9wwgDbYUJ9SjSwc6NPcxGrKk9o0SWDsKWWx4r6mM0W5FJ7Wh11Ca46LLnPC3cFfEg4YDQ==}
peerDependencies:
'@preact/preset-vite': '*'
@@ -3147,7 +3172,7 @@ packages:
remark-external-links: 8.0.0
remark-slug: 6.1.0
rollup: 3.26.3
typescript: 5.0.2
typescript: 5.3.3
vite: 4.4.5(@types/node@20.4.5)(sass@1.64.1)
transitivePeerDependencies:
- encoding
@@ -3586,7 +3611,7 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: true
/@storybook/react-vite@7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.2)(vite@4.4.5):
/@storybook/react-vite@7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)(vite@4.4.5):
resolution: {integrity: sha512-sBMlrLf/zUDUk6bWQLu5/tERW82j9spGMD++O1mdum3eVfPwvsqjtGokTVx/eOLUYA9kqQFdUtjLSn0sS84bTQ==}
engines: {node: '>=16'}
peerDependencies:
@@ -3594,10 +3619,10 @@ packages:
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
vite: ^3.0.0 || ^4.0.0
dependencies:
'@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.0.2)(vite@4.4.5)
'@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.3.3)(vite@4.4.5)
'@rollup/pluginutils': 5.0.2
'@storybook/builder-vite': 7.2.1(typescript@5.0.2)(vite@4.4.5)
'@storybook/react': 7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.2)
'@storybook/builder-vite': 7.2.1(typescript@5.3.3)(vite@4.4.5)
'@storybook/react': 7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
'@vitejs/plugin-react': 3.1.0(vite@4.4.5)
ast-types: 0.14.2
magic-string: 0.30.2
@@ -3614,7 +3639,7 @@ packages:
- vite-plugin-glimmerx
dev: true
/@storybook/react@7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.2):
/@storybook/react@7.2.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3):
resolution: {integrity: sha512-WRAVrSQKAtCoypUrrIXWGOvyGRVkrh73hSkKVC0gEqxWDmjZIZJ1uc3VrUd/yHJdLsqNphcAyU8Ahu52yozubg==}
engines: {node: '>=16.0.0'}
peerDependencies:
@@ -3647,7 +3672,7 @@ packages:
react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0)
ts-dedent: 2.2.0
type-fest: 2.19.0
typescript: 5.0.2
typescript: 5.3.3
util-deprecate: 1.0.2
transitivePeerDependencies:
- encoding
@@ -4073,6 +4098,10 @@ packages:
resolution: {integrity: sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g==}
dev: true
/@types/use-sync-external-store@0.0.3:
resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==}
dev: false
/@types/yargs-parser@21.0.0:
resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
dev: true
@@ -4089,7 +4118,7 @@ packages:
'@types/yargs-parser': 21.0.0
dev: true
/@typescript-eslint/eslint-plugin@6.0.0(@typescript-eslint/parser@6.0.0)(eslint@8.45.0)(typescript@5.0.2):
/@typescript-eslint/eslint-plugin@6.0.0(@typescript-eslint/parser@6.0.0)(eslint@8.45.0)(typescript@5.3.3):
resolution: {integrity: sha512-xuv6ghKGoiq856Bww/yVYnXGsKa588kY3M0XK7uUW/3fJNNULKRfZfSBkMTSpqGG/8ZCXCadfh8G/z/B4aqS/A==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -4101,10 +4130,10 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.6.2
'@typescript-eslint/parser': 6.0.0(eslint@8.45.0)(typescript@5.0.2)
'@typescript-eslint/parser': 6.0.0(eslint@8.45.0)(typescript@5.3.3)
'@typescript-eslint/scope-manager': 6.0.0
'@typescript-eslint/type-utils': 6.0.0(eslint@8.45.0)(typescript@5.0.2)
'@typescript-eslint/utils': 6.0.0(eslint@8.45.0)(typescript@5.0.2)
'@typescript-eslint/type-utils': 6.0.0(eslint@8.45.0)(typescript@5.3.3)
'@typescript-eslint/utils': 6.0.0(eslint@8.45.0)(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 6.0.0
debug: 4.3.4
eslint: 8.45.0
@@ -4114,13 +4143,13 @@ packages:
natural-compare: 1.4.0
natural-compare-lite: 1.4.0
semver: 7.5.4
ts-api-utils: 1.0.1(typescript@5.0.2)
typescript: 5.0.2
ts-api-utils: 1.0.1(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@6.0.0(eslint@8.45.0)(typescript@5.0.2):
/@typescript-eslint/parser@6.0.0(eslint@8.45.0)(typescript@5.3.3):
resolution: {integrity: sha512-TNaufYSPrr1U8n+3xN+Yp9g31vQDJqhXzzPSHfQDLcaO4tU+mCfODPxCwf4H530zo7aUBE3QIdxCXamEnG04Tg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -4132,11 +4161,11 @@ packages:
dependencies:
'@typescript-eslint/scope-manager': 6.0.0
'@typescript-eslint/types': 6.0.0
'@typescript-eslint/typescript-estree': 6.0.0(typescript@5.0.2)
'@typescript-eslint/typescript-estree': 6.0.0(typescript@5.3.3)
'@typescript-eslint/visitor-keys': 6.0.0
debug: 4.3.4
eslint: 8.45.0
typescript: 5.0.2
typescript: 5.3.3
transitivePeerDependencies:
- supports-color
dev: true
@@ -4157,7 +4186,7 @@ packages:
'@typescript-eslint/visitor-keys': 6.0.0
dev: true
/@typescript-eslint/type-utils@6.0.0(eslint@8.45.0)(typescript@5.0.2):
/@typescript-eslint/type-utils@6.0.0(eslint@8.45.0)(typescript@5.3.3):
resolution: {integrity: sha512-ah6LJvLgkoZ/pyJ9GAdFkzeuMZ8goV6BH7eC9FPmojrnX9yNCIsfjB+zYcnex28YO3RFvBkV6rMV6WpIqkPvoQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -4167,12 +4196,12 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 6.0.0(typescript@5.0.2)
'@typescript-eslint/utils': 6.0.0(eslint@8.45.0)(typescript@5.0.2)
'@typescript-eslint/typescript-estree': 6.0.0(typescript@5.3.3)
'@typescript-eslint/utils': 6.0.0(eslint@8.45.0)(typescript@5.3.3)
debug: 4.3.4
eslint: 8.45.0
ts-api-utils: 1.0.1(typescript@5.0.2)
typescript: 5.0.2
ts-api-utils: 1.0.1(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- supports-color
dev: true
@@ -4187,7 +4216,7 @@ packages:
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
/@typescript-eslint/typescript-estree@5.62.0(typescript@5.0.2):
/@typescript-eslint/typescript-estree@5.62.0(typescript@5.3.3):
resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -4202,13 +4231,13 @@ packages:
globby: 11.1.0
is-glob: 4.0.3
semver: 7.5.4
tsutils: 3.21.0(typescript@5.0.2)
typescript: 5.0.2
tsutils: 3.21.0(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/typescript-estree@6.0.0(typescript@5.0.2):
/@typescript-eslint/typescript-estree@6.0.0(typescript@5.3.3):
resolution: {integrity: sha512-2zq4O7P6YCQADfmJ5OTDQTP3ktajnXIRrYAtHM9ofto/CJZV3QfJ89GEaM2BNGeSr1KgmBuLhEkz5FBkS2RQhQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -4223,13 +4252,13 @@ packages:
globby: 11.1.0
is-glob: 4.0.3
semver: 7.5.4
ts-api-utils: 1.0.1(typescript@5.0.2)
typescript: 5.0.2
ts-api-utils: 1.0.1(typescript@5.3.3)
typescript: 5.3.3
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/utils@5.62.0(eslint@8.45.0)(typescript@5.0.2):
/@typescript-eslint/utils@5.62.0(eslint@8.45.0)(typescript@5.3.3):
resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -4240,7 +4269,7 @@ packages:
'@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 5.62.0
'@typescript-eslint/types': 5.62.0
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.0.2)
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.3.3)
eslint: 8.45.0
eslint-scope: 5.1.1
semver: 7.5.4
@@ -4249,7 +4278,7 @@ packages:
- typescript
dev: true
/@typescript-eslint/utils@6.0.0(eslint@8.45.0)(typescript@5.0.2):
/@typescript-eslint/utils@6.0.0(eslint@8.45.0)(typescript@5.3.3):
resolution: {integrity: sha512-SOr6l4NB6HE4H/ktz0JVVWNXqCJTOo/mHnvIte1ZhBQ0Cvd04x5uKZa3zT6tiodL06zf5xxdK8COiDvPnQ27JQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -4260,7 +4289,7 @@ packages:
'@types/semver': 7.5.0
'@typescript-eslint/scope-manager': 6.0.0
'@typescript-eslint/types': 6.0.0
'@typescript-eslint/typescript-estree': 6.0.0(typescript@5.0.2)
'@typescript-eslint/typescript-estree': 6.0.0(typescript@5.3.3)
eslint: 8.45.0
eslint-scope: 5.1.1
semver: 7.5.4
@@ -5757,7 +5786,7 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 6.0.0(eslint@8.45.0)(typescript@5.0.2)
'@typescript-eslint/parser': 6.0.0(eslint@8.45.0)(typescript@5.3.3)
debug: 3.2.7
eslint: 8.45.0
eslint-import-resolver-node: 0.3.7
@@ -5776,7 +5805,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 6.0.0(eslint@8.45.0)(typescript@5.0.2)
'@typescript-eslint/parser': 6.0.0(eslint@8.45.0)(typescript@5.3.3)
array-includes: 3.1.6
array.prototype.flat: 1.3.1
array.prototype.flatmap: 1.3.1
@@ -5861,14 +5890,14 @@ packages:
string.prototype.matchall: 4.0.8
dev: true
/eslint-plugin-storybook@0.6.13(eslint@8.45.0)(typescript@5.0.2):
/eslint-plugin-storybook@0.6.13(eslint@8.45.0)(typescript@5.3.3):
resolution: {integrity: sha512-smd+CS0WH1jBqUEJ3znGS7DU4ayBE9z6lkQAK2yrSUv1+rq8BT/tiI5C/rKE7rmiqiAfojtNYZRhzo5HrulccQ==}
engines: {node: 12.x || 14.x || >= 16}
peerDependencies:
eslint: '>=6'
dependencies:
'@storybook/csf': 0.0.1
'@typescript-eslint/utils': 5.62.0(eslint@8.45.0)(typescript@5.0.2)
'@typescript-eslint/utils': 5.62.0(eslint@8.45.0)(typescript@5.3.3)
eslint: 8.45.0
requireindex: 1.2.0
ts-dedent: 2.2.0
@@ -6726,6 +6755,10 @@ packages:
engines: {node: '>= 4'}
dev: true
/immer@10.0.3:
resolution: {integrity: sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==}
dev: false
/immutable@4.3.1:
resolution: {integrity: sha512-lj9cnmB/kVS0QHsJnYKD1uo3o39nrbKxszjnqS9Fr6NB7bZzW45U6WSGBPKXDL/CvDKqDNPA4r3DoDQ8GTxo2A==}
dev: true
@@ -8291,12 +8324,12 @@ packages:
tween-functions: 1.2.0
dev: true
/react-docgen-typescript@2.2.2(typescript@5.0.2):
/react-docgen-typescript@2.2.2(typescript@5.3.3):
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
peerDependencies:
typescript: '>= 4.3.x'
dependencies:
typescript: 5.0.2
typescript: 5.3.3
dev: true
/react-docgen@6.0.0-alpha.3:
@@ -8368,6 +8401,28 @@ packages:
resolution: {integrity: sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==}
dev: true
/react-redux@9.1.0(@types/react@18.2.15)(react@18.2.0)(redux@5.0.1):
resolution: {integrity: sha512-6qoDzIO+gbrza8h3hjMA9aq4nwVFCKFtY2iLxCtVT38Swyy2C/dJCGBXHeHLtx6qlg/8qzc2MrhOeduf5K32wQ==}
peerDependencies:
'@types/react': ^18.2.25
react: ^18.0
react-native: '>=0.69'
redux: ^5.0.0
peerDependenciesMeta:
'@types/react':
optional: true
react-native:
optional: true
redux:
optional: true
dependencies:
'@types/react': 18.2.15
'@types/use-sync-external-store': 0.0.3
react: 18.2.0
redux: 5.0.1
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
/react-refresh@0.14.0:
resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
engines: {node: '>=0.10.0'}
@@ -8565,6 +8620,18 @@ packages:
strip-indent: 4.0.0
dev: true
/redux-thunk@3.1.0(redux@5.0.1):
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
peerDependencies:
redux: ^5.0.0
dependencies:
redux: 5.0.1
dev: false
/redux@5.0.1:
resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
dev: false
/regenerate-unicode-properties@10.1.0:
resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==}
engines: {node: '>=4'}
@@ -8649,6 +8716,10 @@ packages:
engines: {node: '>=0.10.5'}
dev: true
/reselect@5.1.0:
resolution: {integrity: sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==}
dev: false
/resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -9527,13 +9598,13 @@ packages:
engines: {node: '>=12'}
dev: true
/ts-api-utils@1.0.1(typescript@5.0.2):
/ts-api-utils@1.0.1(typescript@5.3.3):
resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==}
engines: {node: '>=16.13.0'}
peerDependencies:
typescript: '>=4.2.0'
dependencies:
typescript: 5.0.2
typescript: 5.3.3
dev: true
/ts-dedent@2.2.0:
@@ -9556,14 +9627,14 @@ packages:
/tslib@2.6.1:
resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==}
/tsutils@3.21.0(typescript@5.0.2):
/tsutils@3.21.0(typescript@5.3.3):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
peerDependencies:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
dependencies:
tslib: 1.14.1
typescript: 5.0.2
typescript: 5.3.3
dev: true
/tween-functions@1.2.0:
@@ -9652,9 +9723,9 @@ packages:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
dev: true
/typescript@5.0.2:
resolution: {integrity: sha512-wVORMBGO/FAs/++blGNeAVdbNKtIh1rbBL2EyQ1+J9lClJ93KiiKe8PmFIVdXhHcyv44SL9oglmfeSsndo0jRw==}
engines: {node: '>=12.20'}
/typescript@5.3.3:
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
@@ -9814,6 +9885,14 @@ packages:
react: 18.2.0
tslib: 2.6.1
/use-sync-external-store@1.2.0(react@18.2.0):
resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
dev: false
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}

View File

@@ -1,3 +1,12 @@
import { Provider } from 'react-redux'
import { Router } from '@/router'
import { store } from '@/services/store.ts'
export function App() {
return <div>Hello</div>
return (
<Provider store={store}>
<Router />
</Provider>
)
}

View File

@@ -0,0 +1,139 @@
import { useMemo, useState } from 'react'
import { Link, useSearchParams } from 'react-router-dom'
import { Button, TextField } from '@/components'
import { Sort, Table, TableBody, TableCell, TableHeader, TableRow } from '@/components/ui/table'
import {
useCreateDeckMutation,
useDeleteDeckMutation,
useGetDecksQuery,
} from '@/services/decks/decks.service.ts'
const columns = [
{
key: 'name',
title: 'Name',
sortable: true,
},
{
key: 'cardsCount',
title: 'Cards',
sortable: true,
},
{
key: 'updated',
title: 'Last updated',
sortable: true,
},
{
key: 'author.name',
title: 'Created by',
sortable: true,
},
{
key: '',
title: '',
sortable: false,
},
]
export const Decks = () => {
const [search, setSearch] = useSearchParams()
const [name, setName] = useState('')
const orderBy = JSON.parse(search.get('orderBy') ?? 'null')
const setOrderBy = (value: Sort) => {
search.set('orderBy', JSON.stringify(value))
setSearch(search)
}
const orderByString = useMemo(() => {
if (!orderBy) return null
return `${orderBy.key}-${orderBy.direction}`
}, [orderBy])
const { data, isLoading, error } = useGetDecksQuery({
name,
orderBy: orderByString,
})
const [createDeck, { isLoading: isDeckBeingCreated }] = useCreateDeckMutation()
const [deleteDeck, { isLoading: isDeckBeingDeleted }] = useDeleteDeckMutation()
if (isLoading) {
return <h1>Loading...</h1>
}
if (error) {
return <h1>Error: {JSON.stringify(error)}...</h1>
}
return (
<div
style={{
maxWidth: 1280,
padding: '24px 137px',
display: 'flex',
flexDirection: 'column',
gap: '24px',
alignItems: 'center',
margin: '0 auto',
}}
>
<Link to={'login'}>Login</Link>
<TextField onValueChange={setName} value={name} label={'Search'} />
<Button
disabled={isDeckBeingCreated}
onClick={() => {
createDeck({
name: '🥳 new card',
})
}}
>
Create deck
</Button>
<Table width={'100%'}>
<TableHeader columns={columns} sort={orderBy} onSort={setOrderBy} />
<TableBody>
{data?.items?.map(deck => {
return (
<TableRow key={deck.id}>
<TableCell>{deck.name}</TableCell>
<TableCell>{deck.cardsCount}</TableCell>
<TableCell>{new Date(deck.updated).toLocaleDateString('ru-RU')}</TableCell>
<TableCell>{deck.author.name}</TableCell>
<TableCell>
<button
style={{ all: 'unset' }}
onClick={() => deleteDeck({ id: deck.id })}
disabled={isDeckBeingDeleted}
>
<TrashOutline />
</button>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</div>
)
}
const TrashOutline = () => {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clipPath="url(#clip0_53159_1173)">
<path
d="M13.9999 4.00011H10.6666V2.88677C10.6509 2.45998 10.4667 2.05681 10.1543 1.76563C9.84188 1.47446 9.42675 1.31903 8.99992 1.33344H6.99992C6.57309 1.31903 6.15796 1.47446 5.84554 1.76563C5.53312 2.05681 5.34889 2.45998 5.33325 2.88677V4.00011H1.99992C1.82311 4.00011 1.65354 4.07034 1.52851 4.19537C1.40349 4.32039 1.33325 4.48996 1.33325 4.66677C1.33325 4.84358 1.40349 5.01315 1.52851 5.13818C1.65354 5.2632 1.82311 5.33344 1.99992 5.33344H2.66659V12.6668C2.66659 13.1972 2.8773 13.7059 3.25237 14.081C3.62744 14.4561 4.13615 14.6668 4.66659 14.6668H11.3333C11.8637 14.6668 12.3724 14.4561 12.7475 14.081C13.1225 13.7059 13.3333 13.1972 13.3333 12.6668V5.33344H13.9999C14.1767 5.33344 14.3463 5.2632 14.4713 5.13818C14.5963 5.01315 14.6666 4.84358 14.6666 4.66677C14.6666 4.48996 14.5963 4.32039 14.4713 4.19537C14.3463 4.07034 14.1767 4.00011 13.9999 4.00011ZM6.66659 2.88677C6.66659 2.78011 6.80659 2.66677 6.99992 2.66677H8.99992C9.19325 2.66677 9.33325 2.78011 9.33325 2.88677V4.00011H6.66659V2.88677ZM11.9999 12.6668C11.9999 12.8436 11.9297 13.0132 11.8047 13.1382C11.6796 13.2632 11.5101 13.3334 11.3333 13.3334H4.66659C4.48977 13.3334 4.32021 13.2632 4.19518 13.1382C4.07016 13.0132 3.99992 12.8436 3.99992 12.6668V5.33344H11.9999V12.6668Z"
fill="white"
/>
</g>
<defs>
<clipPath id="clip0_53159_1173">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
)
}

View File

@@ -54,16 +54,16 @@ export const TableHeader: FC<
}
return (
<thead {...restProps}>
<tr>
<TableHead {...restProps}>
<TableRow>
{columns.map(({ title, key, sortable }) => (
<th key={key} onClick={handleSort(key, sortable)}>
<TableHeadCell key={key} onClick={handleSort(key, sortable)}>
{title}
{sort && sort.key === key && <span>{sort.direction === 'asc' ? '▲' : '▼'}</span>}
</th>
</TableHeadCell>
))}
</tr>
</thead>
</TableRow>
</TableHead>
)
}
export const TableBody = forwardRef<ElementRef<'tbody'>, ComponentPropsWithoutRef<'tbody'>>(

15
src/hooks/useDebounce.ts Normal file
View File

@@ -0,0 +1,15 @@
import { useEffect, useState } from 'react'
export function useDebounce<T>(value: T, delay?: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay || 500)
return () => {
clearTimeout(timer)
}
}, [value, delay])
return debouncedValue
}

View File

@@ -2,14 +2,12 @@ import '@fontsource/roboto/400.css'
import '@fontsource/roboto/700.css'
import './styles/index.scss'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { App } from '@/App'
createRoot(document.getElementById('root')!).render(
<StrictMode>
// <StrictMode>
<App />
</StrictMode>
// </StrictMode>
)

40
src/router.tsx Normal file
View File

@@ -0,0 +1,40 @@
import {
createBrowserRouter,
Navigate,
Outlet,
RouteObject,
RouterProvider,
} from 'react-router-dom'
import { Decks } from '@/components/decks/decks.tsx'
const publicRoutes: RouteObject[] = [
{
path: '/login',
element: <div>login</div>,
},
]
const privateRoutes: RouteObject[] = [
{
path: '/',
element: <Decks />,
},
]
const router = createBrowserRouter([
{
element: <PrivateRoutes />,
children: privateRoutes,
},
...publicRoutes,
])
export const Router = () => {
return <RouterProvider router={router} />
}
function PrivateRoutes() {
const isAuthenticated = true
return isAuthenticated ? <Outlet /> : <Navigate to="/login" />
}

14
src/services/base-api.ts Normal file
View File

@@ -0,0 +1,14 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const baseApi = createApi({
reducerPath: 'baseApi',
tagTypes: ['Decks'],
baseQuery: fetchBaseQuery({
baseUrl: 'https://api.flashcards.andrii.es',
credentials: 'include',
prepareHeaders: headers => {
headers.append('x-auth-skip', 'true')
},
}),
endpoints: () => ({}),
})

View File

@@ -0,0 +1,11 @@
export type Pagination = {
totalItems: number
totalPages: number
currentPage: number
itemsPerPage: number
}
export type PaginatedResponse<T> = {
items: T
pagination: Pagination
}

View File

@@ -0,0 +1,33 @@
import { CreateDeckArgs, Deck, DeckResponse, DeleteDeckArgs, GetDecksArgs } from './decks.types.ts'
import { baseApi } from '@/services/base-api.ts'
export const DecksService = baseApi.injectEndpoints({
endpoints: builder => {
return {
getDecks: builder.query<DeckResponse, GetDecksArgs | void>({
query: args => ({
url: `v2/decks`,
params: args ?? undefined,
}),
providesTags: ['Decks'],
}),
createDeck: builder.mutation<Deck, CreateDeckArgs>({
query: args => ({
url: 'v1/decks',
body: args,
method: 'POST',
}),
invalidatesTags: ['Decks'],
}),
deleteDeck: builder.mutation<void, DeleteDeckArgs>({
query: args => ({
url: `v1/decks/${args.id}`,
method: 'DELETE',
}),
invalidatesTags: ['Decks'],
}),
}
},
})
export const { useGetDecksQuery, useCreateDeckMutation, useDeleteDeckMutation } = DecksService

View File

@@ -0,0 +1,40 @@
import { PaginatedResponse } from '@/services/common.types.ts'
export type DeckResponse = PaginatedResponse<Deck[]>
export type Deck = {
author: DeckAuthor
id: string
userId: string
name: string
isPrivate: boolean
cover: string
created: string
updated: string
cardsCount: number
}
export type DeckAuthor = {
id: string
name: string
}
export type GetDecksArgs = {
orderBy?: string | null
minCardsCount?: number
maxCardsCount?: number
name?: string
authorId?: string
currentPage?: number
itemsPerPage?: number
}
export type CreateDeckArgs = {
name: string
cover?: File | null
isPrivate?: boolean
}
export type DeleteDeckArgs = {
id: string
}

15
src/services/store.ts Normal file
View File

@@ -0,0 +1,15 @@
import { configureStore } from '@reduxjs/toolkit'
import { setupListeners } from '@reduxjs/toolkit/query/react'
import { baseApi } from '@/services/base-api.ts'
export const store = configureStore({
reducer: {
[baseApi.reducerPath]: baseApi.reducer,
},
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(baseApi.middleware),
})
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>
setupListeners(store.dispatch)