diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
new file mode 100644
index 0000000..02b915b
--- /dev/null
+++ b/.idea/git_toolbox_prj.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
index cd7c70a..16315c9 100644
--- a/package.json
+++ b/package.json
@@ -13,12 +13,18 @@
"@it-incubator/prettier-config": "^0.1.2",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
+ "@reduxjs/toolkit": "^2.2.6",
+ "@types/javascript-time-ago": "^2.0.8",
"clsx": "^2.1.1",
+ "javascript-time-ago": "^2.5.10",
"next": "14.2.5",
+ "next-redux-wrapper": "^8.1.0",
"prettier": "^3.3.2",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "7.52.1",
+ "react-redux": "^9.1.2",
+ "react-time-ago": "^7.3.3",
"zod": "^3.23.8"
},
"devDependencies": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e56dd26..675100d 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,12 +20,24 @@ importers:
'@radix-ui/react-icons':
specifier: ^1.3.0
version: 1.3.0(react@18.3.1)
+ '@reduxjs/toolkit':
+ specifier: ^2.2.6
+ version: 2.2.6(react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
+ '@types/javascript-time-ago':
+ specifier: ^2.0.8
+ version: 2.0.8
clsx:
specifier: ^2.1.1
version: 2.1.1
+ javascript-time-ago:
+ specifier: ^2.5.10
+ version: 2.5.10
next:
specifier: 14.2.5
version: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ next-redux-wrapper:
+ specifier: ^8.1.0
+ version: 8.1.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
prettier:
specifier: ^3.3.2
version: 3.3.2
@@ -38,6 +50,12 @@ importers:
react-hook-form:
specifier: 7.52.1
version: 7.52.1(react@18.3.1)
+ react-redux:
+ specifier: ^9.1.2
+ version: 9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1)
+ react-time-ago:
+ specifier: ^7.3.3
+ version: 7.3.3(javascript-time-ago@2.5.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
zod:
specifier: ^3.23.8
version: 3.23.8
@@ -329,6 +347,17 @@ packages:
'@types/react':
optional: true
+ '@reduxjs/toolkit@2.2.6':
+ resolution: {integrity: sha512-kH0r495c5z1t0g796eDQAkYbEQ3a1OLYN9o8jQQVZyKyw367pfRGS+qZLkHYvFHiUUdafpoSlQ2QYObIApjPWA==}
+ 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
+
'@rushstack/eslint-patch@1.10.3':
resolution: {integrity: sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==}
@@ -338,6 +367,9 @@ packages:
'@swc/helpers@0.5.5':
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
+ '@types/javascript-time-ago@2.0.8':
+ resolution: {integrity: sha512-X77q3xUzWVn0qohgurKE1G5NiXZjee8VbLqaukW/HXVkz7bdCFJgOPQ3JVB4IkrDhMS4CviFTmpZuNVg0i2QFA==}
+
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
@@ -353,6 +385,9 @@ packages:
'@types/react@18.3.3':
resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==}
+ '@types/use-sync-external-store@0.0.3':
+ resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==}
+
'@typescript-eslint/parser@7.2.0':
resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -922,6 +957,9 @@ packages:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
+ immer@10.1.1:
+ resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
+
import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
@@ -1070,6 +1108,9 @@ packages:
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
+ javascript-time-ago@2.5.10:
+ resolution: {integrity: sha512-EUxp4BP74QH8xiYHyeSHopx1XhMMJ9qEX4rcBdFtpVWmKRdzpxbNzz2GSbuekZr5wt0rmLehuyp0PE34EAJT9g==}
+
jiti@1.21.6:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
@@ -1137,6 +1178,9 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+ memoize-one@6.0.0:
+ resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
+
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -1180,6 +1224,13 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ next-redux-wrapper@8.1.0:
+ resolution: {integrity: sha512-2hIau0hcI6uQszOtrvAFqgc0NkZegKYhBB7ZAKiG3jk7zfuQb4E7OV9jfxViqqojh3SEHdnFfPkN9KErttUKuw==}
+ peerDependencies:
+ next: '>=9'
+ react: '*'
+ react-redux: '*'
+
next@14.2.5:
resolution: {integrity: sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==}
engines: {node: '>=18.17.0'}
@@ -1291,6 +1342,9 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
+ performance-now@2.1.0:
+ resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
+
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
@@ -1379,6 +1433,9 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ raf@3.4.1:
+ resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==}
+
react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies:
@@ -1393,6 +1450,25 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+ react-redux@9.1.2:
+ resolution: {integrity: sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==}
+ peerDependencies:
+ '@types/react': ^18.2.25
+ react: ^18.0
+ redux: ^5.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ redux:
+ optional: true
+
+ react-time-ago@7.3.3:
+ resolution: {integrity: sha512-5kh2Kuu/UhHzcZrGvf3GUrF2d+IXjkIXif5MR2iDWIfSqQuBW27/ejN/tmzJBRyPiryYTgbDIG6AZFJ4RW3yfw==}
+ peerDependencies:
+ javascript-time-ago: ^2.3.7
+ react: '>=0.16.8'
+ react-dom: '>=0.16.8'
+
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@@ -1404,6 +1480,14 @@ packages:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
+ redux-thunk@3.1.0:
+ resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
+ peerDependencies:
+ redux: ^5.0.0
+
+ redux@5.0.1:
+ resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
+
reflect.getprototypeof@1.0.6:
resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==}
engines: {node: '>= 0.4'}
@@ -1412,6 +1496,12 @@ packages:
resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
engines: {node: '>= 0.4'}
+ relative-time-format@1.1.6:
+ resolution: {integrity: sha512-aCv3juQw4hT1/P/OrVltKWLlp15eW1GRcwP1XdxHrPdZE9MtgqFpegjnTjLhi2m2WI9MT/hQQtE+tjEWG1hgkQ==}
+
+ reselect@5.1.1:
+ resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
+
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -1643,6 +1733,11 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ use-sync-external-store@1.2.2:
+ resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@@ -1906,6 +2001,16 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.3
+ '@reduxjs/toolkit@2.2.6(react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1))(react@18.3.1)':
+ dependencies:
+ immer: 10.1.1
+ redux: 5.0.1
+ redux-thunk: 3.1.0(redux@5.0.1)
+ reselect: 5.1.1
+ optionalDependencies:
+ react: 18.3.1
+ react-redux: 9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1)
+
'@rushstack/eslint-patch@1.10.3': {}
'@swc/counter@0.1.3': {}
@@ -1915,6 +2020,8 @@ snapshots:
'@swc/counter': 0.1.3
tslib: 2.6.3
+ '@types/javascript-time-ago@2.0.8': {}
+
'@types/json5@0.0.29': {}
'@types/node@20.14.10':
@@ -1932,6 +2039,8 @@ snapshots:
'@types/prop-types': 15.7.12
csstype: 3.1.3
+ '@types/use-sync-external-store@0.0.3': {}
+
'@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.3)':
dependencies:
'@typescript-eslint/scope-manager': 7.2.0
@@ -2729,6 +2838,8 @@ snapshots:
ignore@5.3.1: {}
+ immer@10.1.1: {}
+
import-fresh@3.3.0:
dependencies:
parent-module: 1.0.1
@@ -2876,6 +2987,10 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
+ javascript-time-ago@2.5.10:
+ dependencies:
+ relative-time-format: 1.1.6
+
jiti@1.21.6: {}
js-tokens@4.0.0: {}
@@ -2934,6 +3049,8 @@ snapshots:
lru-cache@10.4.3: {}
+ memoize-one@6.0.0: {}
+
merge2@1.4.1: {}
micromatch@4.0.7:
@@ -2971,6 +3088,12 @@ snapshots:
natural-compare@1.4.0: {}
+ next-redux-wrapper@8.1.0(next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1))(react@18.3.1):
+ dependencies:
+ next: 14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ react: 18.3.1
+ react-redux: 9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1)
+
next@14.2.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 14.2.5
@@ -3091,6 +3214,8 @@ snapshots:
path-type@4.0.0: {}
+ performance-now@2.1.0: {}
+
picocolors@1.0.1: {}
picomatch@2.3.1: {}
@@ -3160,6 +3285,10 @@ snapshots:
queue-microtask@1.2.3: {}
+ raf@3.4.1:
+ dependencies:
+ performance-now: 2.1.0
+
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
@@ -3172,6 +3301,24 @@ snapshots:
react-is@16.13.1: {}
+ react-redux@9.1.2(@types/react@18.3.3)(react@18.3.1)(redux@5.0.1):
+ dependencies:
+ '@types/use-sync-external-store': 0.0.3
+ react: 18.3.1
+ use-sync-external-store: 1.2.2(react@18.3.1)
+ optionalDependencies:
+ '@types/react': 18.3.3
+ redux: 5.0.1
+
+ react-time-ago@7.3.3(javascript-time-ago@2.5.10)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ javascript-time-ago: 2.5.10
+ memoize-one: 6.0.0
+ prop-types: 15.8.1
+ raf: 3.4.1
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
react@18.3.1:
dependencies:
loose-envify: 1.4.0
@@ -3184,6 +3331,12 @@ snapshots:
dependencies:
picomatch: 2.3.1
+ redux-thunk@3.1.0(redux@5.0.1):
+ dependencies:
+ redux: 5.0.1
+
+ redux@5.0.1: {}
+
reflect.getprototypeof@1.0.6:
dependencies:
call-bind: 1.0.7
@@ -3201,6 +3354,10 @@ snapshots:
es-errors: 1.3.0
set-function-name: 2.0.2
+ relative-time-format@1.1.6: {}
+
+ reselect@5.1.1: {}
+
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@@ -3484,6 +3641,10 @@ snapshots:
dependencies:
punycode: 2.3.1
+ use-sync-external-store@1.2.2(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
util-deprecate@1.0.2: {}
which-boxed-primitive@1.0.2:
diff --git a/src/components/loader/loader.module.css b/src/components/loader/loader.module.css
new file mode 100644
index 0000000..52932f0
--- /dev/null
+++ b/src/components/loader/loader.module.css
@@ -0,0 +1,92 @@
+.loader {
+ position: relative;
+ width: 130px;
+ height: 100px;
+ background-repeat: no-repeat;
+ background-image: linear-gradient(#0277bd, #0277bd), linear-gradient(#29b6f6, #4fc3f7),
+ linear-gradient(#29b6f6, #4fc3f7);
+ background-size:
+ 80px 70px,
+ 30px 50px,
+ 30px 30px;
+ background-position:
+ 0 0,
+ 80px 20px,
+ 100px 40px;
+}
+.loader:after {
+ content: '';
+ position: absolute;
+ bottom: 10px;
+ left: 12px;
+ width: 10px;
+ height: 10px;
+ background: #fff;
+ border-radius: 50%;
+ box-sizing: content-box;
+ border: 10px solid #000;
+ box-shadow:
+ 78px 0 0 -10px #fff,
+ 78px 0 #000;
+ animation: wheelSk 0.75s ease-in infinite alternate;
+}
+
+.loader:before {
+ content: '';
+ position: absolute;
+ right: 100%;
+ top: 0px;
+ height: 70px;
+ width: 70px;
+ background-image: linear-gradient(#fff 45px, transparent 0),
+ linear-gradient(#fff 45px, transparent 0), linear-gradient(#fff 45px, transparent 0);
+ background-repeat: no-repeat;
+ background-size: 30px 4px;
+ background-position:
+ 0px 11px,
+ 8px 35px,
+ 0px 60px;
+ animation: lineDropping 0.75s linear infinite;
+}
+
+@keyframes wheelSk {
+ 0%,
+ 50%,
+ 100% {
+ transform: translatey(0);
+ }
+ 30%,
+ 90% {
+ transform: translatey(-3px);
+ }
+}
+
+@keyframes lineDropping {
+ 0% {
+ background-position:
+ 100px 11px,
+ 115px 35px,
+ 105px 60px;
+ opacity: 1;
+ }
+ 50% {
+ background-position:
+ 0px 11px,
+ 20px 35px,
+ 5px 60px;
+ }
+ 60% {
+ background-position:
+ -30px 11px,
+ 0px 35px,
+ -10px 60px;
+ }
+ 75%,
+ 100% {
+ background-position:
+ -30px 11px,
+ -30px 35px,
+ -30px 60px;
+ opacity: 0;
+ }
+}
diff --git a/src/components/loader/loader.tsx b/src/components/loader/loader.tsx
new file mode 100644
index 0000000..f3ad515
--- /dev/null
+++ b/src/components/loader/loader.tsx
@@ -0,0 +1,5 @@
+import s from './loader.module.css'
+
+export const Loader = () => {
+ return
+}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index d987447..d607aba 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -1,11 +1,21 @@
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
+import { wrapper } from '@/store'
+import { Provider } from 'react-redux'
+import TimeAgo from 'javascript-time-ago'
-export default function App({ Component, pageProps }: AppProps) {
+import en from 'javascript-time-ago/locale/en'
+
+TimeAgo.addDefaultLocale(en)
+
+export default function App({ Component, ...rest }: AppProps) {
+ const { store, props } = wrapper.useWrappedStore(rest)
return (
-
-
-
+
+
+
+
+
)
}
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index e78f5e1..6fdc7a8 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,5 +1,9 @@
import Head from 'next/head'
import Link from 'next/link'
+import ReactTimeAgo from 'react-time-ago'
+import { Loader } from '@/components/loader/loader'
+import { useState } from 'react'
+import { useGetPublicPostsQuery } from '@/services/inctagram.public-posts.service'
export default function Home() {
return (
@@ -13,11 +17,54 @@ export default function Home() {
}
function Content() {
+ const [pageSize, setPageSize] = useState(4)
+ const { data, isLoading, isError, error } = useGetPublicPostsQuery({
+ pageSize,
+ })
+ if (isLoading) {
+ return (
+
+
+
+ )
+ }
+
+ if (isError) {
+ return Error: {JSON.stringify(error, null, 2)}
+ }
+
return (
+
+
+ {data?.items?.map(post => {
+ return (
+
+

+
+

+
{post.userName}
+
+
+ {post.description &&
{post.description}
}
+
+ )
+ })}
+
)
}
diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx
new file mode 100644
index 0000000..9270dd7
--- /dev/null
+++ b/src/pages/profile.tsx
@@ -0,0 +1,72 @@
+import {
+ useLazyGetUserProfileQuery,
+ useUpdateUserProfileMutation,
+} from '@/services/inctagram.profile.service'
+import { FormInput } from '@/form/form-input'
+import { useForm } from 'react-hook-form'
+import { UpdateUserProfileArgs } from '@/services/inctagram.types'
+import { useUploadImageMutation } from '@/services/inctagram.service'
+import { useState } from 'react'
+
+export default function Profile() {
+ const [getProfile, profile] = useLazyGetUserProfileQuery()
+ const [files, setFiles] = useState(null)
+ const [mutate, { isLoading }] = useUpdateUserProfileMutation()
+ const [uploadImage, { isLoading: loadingImage }] = useUploadImageMutation()
+ const { control, handleSubmit } = useForm({
+ defaultValues: async () => {
+ const { data } = await getProfile()
+
+ if (!data) {
+ return {} as UpdateUserProfileArgs
+ }
+
+ return {
+ ...data,
+ firstName: data.firstName ?? '',
+ lastName: data.lastName ?? '',
+ dateOfBirth: '1961-07-13T17:58:17.000Z',
+ aboutMe: 'HELLO BITHCHES',
+ }
+ },
+ })
+
+ // const onSubmit = handleSubmit(data => {
+ // mutate(data)
+ // })
+ const onSubmit = handleSubmit(mutate)
+ return (
+
+ Profile
+
{profile.data?.userName}
+
+ {profile.data?.firstName} {profile.data?.lastName}
+
+
+
+
+ )
+}
diff --git a/src/services/inctagram.profile.service.ts b/src/services/inctagram.profile.service.ts
new file mode 100644
index 0000000..2040462
--- /dev/null
+++ b/src/services/inctagram.profile.service.ts
@@ -0,0 +1,27 @@
+import { inctagramService } from '@/services/inctagram.service'
+import { GetUserProfileResponse, UpdateUserProfileArgs } from '@/services/inctagram.types'
+
+export const inctagramProfileService = inctagramService.injectEndpoints({
+ endpoints: builder => {
+ return {
+ getUserProfile: builder.query({
+ query: () => {
+ return 'v1/users/profile'
+ },
+ providesTags: ['UserProfile'],
+ }),
+ updateUserProfile: builder.mutation({
+ query: body => {
+ return {
+ url: '/v1/users/profile',
+ method: 'PUT',
+ body,
+ }
+ },
+ invalidatesTags: ['UserProfile'],
+ }),
+ }
+ },
+})
+
+export const { useLazyGetUserProfileQuery, useUpdateUserProfileMutation } = inctagramProfileService
diff --git a/src/services/inctagram.public-posts.service.ts b/src/services/inctagram.public-posts.service.ts
new file mode 100644
index 0000000..095a74b
--- /dev/null
+++ b/src/services/inctagram.public-posts.service.ts
@@ -0,0 +1,29 @@
+import { inctagramService } from '@/services/inctagram.service'
+import {
+ GetPostsArgs,
+ GetPostsResponse,
+ GetUserProfileResponse,
+ UpdateUserProfileArgs,
+} from '@/services/inctagram.types'
+
+export const inctagramPublicPostsService = inctagramService.injectEndpoints({
+ endpoints: builder => {
+ return {
+ getPublicPosts: builder.query({
+ query: ({ pageSize }) => {
+ return {
+ url: `v1/public-posts/all/`,
+ params: {
+ pageSize,
+ sortBy: 'createdAt',
+ sortDirection: 'desc',
+ },
+ }
+ },
+ providesTags: ['PublicPosts'],
+ }),
+ }
+ },
+})
+
+export const { useGetPublicPostsQuery } = inctagramPublicPostsService
diff --git a/src/services/inctagram.service.ts b/src/services/inctagram.service.ts
new file mode 100644
index 0000000..4edba21
--- /dev/null
+++ b/src/services/inctagram.service.ts
@@ -0,0 +1,44 @@
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
+
+const token =
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjczNSwiaWF0IjoxNzIxNDE0NTgzLCJleHAiOjE3MjE0MTgxODN9.O1Kao-oNtMDNtCPU0-exGlif7YJegpIVPxeZm1KC758'
+
+export const inctagramService = createApi({
+ // keepUnusedDataFor: 0,
+ tagTypes: ['UserProfile', 'PublicPosts'],
+ reducerPath: 'inctagramService',
+ baseQuery: fetchBaseQuery({
+ baseUrl: 'https://inctagram.work/api',
+ prepareHeaders: headers => {
+ headers.set('Authorization', `Bearer ${token}`)
+ return headers
+ },
+ }),
+ endpoints: builder => ({
+ uploadImage: builder.mutation({
+ query({ files }) {
+ const formData = new FormData()
+ Array.from(files).forEach(file => {
+ formData.append('file', file)
+ })
+ console.log(formData)
+ return {
+ method: 'POST',
+ body: formData,
+ url: '/v1/posts/image',
+ }
+ },
+ }),
+ deleteImage: builder.mutation({
+ query({ id }) {
+ return {
+ method: 'DELETE',
+ url: `/v1/posts/image/${id}`,
+ params: {},
+ }
+ },
+ }),
+ }),
+})
+
+export const { useUploadImageMutation } = inctagramService
diff --git a/src/services/inctagram.types.ts b/src/services/inctagram.types.ts
new file mode 100644
index 0000000..cbaf9b3
--- /dev/null
+++ b/src/services/inctagram.types.ts
@@ -0,0 +1,60 @@
+export interface GetPostsResponse {
+ totalCount: number
+ pageSize: number
+ items: Item[]
+ totalUsers: number
+}
+
+export interface GetPostsArgs {
+ pageSize: number
+}
+
+export interface Item {
+ id: number
+ userName: string
+ description: string
+ location?: any
+ images: Image[]
+ createdAt: string
+ updatedAt: string
+ avatarOwner?: string
+ ownerId: number
+ owner: Owner
+ likesCount: number
+ isLiked: boolean
+}
+
+export interface Owner {
+ firstName?: string
+ lastName?: string
+}
+
+export interface Image {
+ url: string
+ width: number
+ height: number
+ fileSize: number
+ createdAt: string
+ uploadId: string
+}
+
+export interface GetUserProfileResponse {
+ id: number
+ userName: string
+ firstName?: any
+ lastName?: any
+ city?: any
+ dateOfBirth?: any
+ aboutMe?: any
+ createdAt: string
+ avatars: any[]
+}
+
+export interface UpdateUserProfileArgs {
+ userName: string
+ firstName: string
+ lastName: string
+ city?: string
+ dateOfBirth?: string
+ aboutMe?: string
+}
diff --git a/src/store.ts b/src/store.ts
new file mode 100644
index 0000000..0688577
--- /dev/null
+++ b/src/store.ts
@@ -0,0 +1,23 @@
+import { Action, configureStore, ThunkAction } from '@reduxjs/toolkit'
+import { createWrapper } from 'next-redux-wrapper'
+import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
+import { inctagramService } from '@/services/inctagram.service'
+
+const makeStore = () =>
+ configureStore({
+ reducer: {
+ [inctagramService.reducerPath]: inctagramService.reducer,
+ },
+ middleware: getDefaultMiddleware => getDefaultMiddleware().concat(inctagramService.middleware),
+ devTools: true,
+ })
+
+export type AppStore = ReturnType
+export type AppState = ReturnType
+export type AppDispatch = AppStore['dispatch']
+export type AppThunk = ThunkAction
+
+export const useAppDispatch = () => useDispatch()
+export const useAppSelector: TypedUseSelectorHook = useSelector
+
+export const wrapper = createWrapper(makeStore)