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} +

+
+ + + + + +
{ + e.preventDefault() + if (!files || !files.length) { + return + } + uploadImage({ files }) + }} + > + { + setFiles(e.currentTarget.files) + }} + /> + +
+
+ ) +} 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)