diff --git a/package.json b/package.json
index 78fa372..ea17c00 100644
--- a/package.json
+++ b/package.json
@@ -16,13 +16,18 @@
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-slot": "^1.1.0",
+ "@reduxjs/toolkit": "^2.2.7",
+ "@types/react-timeago": "^4.1.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"lucide-react": "^0.439.0",
"next": "14.2.7",
+ "next-redux-wrapper": "^8.1.0",
"react": "^18",
"react-dom": "^18",
"react-hook-form": "^7.53.0",
+ "react-redux": "^9.1.2",
+ "react-timeago": "^7.2.0",
"tailwind-merge": "^2.5.2",
"zod": "^3.23.8"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8d3d7fe..33d1917 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,6 +23,12 @@ importers:
'@radix-ui/react-slot':
specifier: ^1.1.0
version: 1.1.0(@types/react@18.3.5)(react@18.3.1)
+ '@reduxjs/toolkit':
+ specifier: ^2.2.7
+ version: 2.2.7(react-redux@9.1.2(@types/react@18.3.5)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
+ '@types/react-timeago':
+ specifier: ^4.1.7
+ version: 4.1.7
class-variance-authority:
specifier: ^0.7.0
version: 0.7.0
@@ -35,6 +41,9 @@ importers:
next:
specifier: 14.2.7
version: 14.2.7(@babel/core@7.25.2)(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.7(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@9.1.2(@types/react@18.3.5)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
react:
specifier: ^18
version: 18.3.1
@@ -44,6 +53,12 @@ importers:
react-hook-form:
specifier: ^7.53.0
version: 7.53.0(react@18.3.1)
+ react-redux:
+ specifier: ^9.1.2
+ version: 9.1.2(@types/react@18.3.5)(react@18.3.1)(redux@5.0.1)
+ react-timeago:
+ specifier: ^7.2.0
+ version: 7.2.0(react@18.3.1)
tailwind-merge:
specifier: ^2.5.2
version: 2.5.2
@@ -1503,6 +1518,17 @@ packages:
'@radix-ui/rect@1.1.0':
resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==}
+ '@reduxjs/toolkit@2.2.7':
+ resolution: {integrity: sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==}
+ 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.4':
resolution: {integrity: sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==}
@@ -1845,6 +1871,9 @@ packages:
'@types/react-dom@18.3.0':
resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==}
+ '@types/react-timeago@4.1.7':
+ resolution: {integrity: sha512-ogD4Ror/hDG+pQggCX+TgPgJ8W2jeeUxsgNU485Qpm0Ma+E2TND2EJuKwK5+sxlkDXDEgsHradO0zWBkTgLzNg==}
+
'@types/react@18.3.5':
resolution: {integrity: sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==}
@@ -1863,6 +1892,9 @@ packages:
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+ '@types/use-sync-external-store@0.0.3':
+ resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==}
+
'@types/uuid@9.0.8':
resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
@@ -3334,6 +3366,9 @@ packages:
engines: {node: '>=16.x'}
hasBin: true
+ immer@10.1.1:
+ resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
+
import-fresh@3.3.0:
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
engines: {node: '>=6'}
@@ -3864,6 +3899,13 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+ next-redux-wrapper@8.1.0:
+ resolution: {integrity: sha512-2hIau0hcI6uQszOtrvAFqgc0NkZegKYhBB7ZAKiG3jk7zfuQb4E7OV9jfxViqqojh3SEHdnFfPkN9KErttUKuw==}
+ peerDependencies:
+ next: '>=9'
+ react: '*'
+ react-redux: '*'
+
next@14.2.7:
resolution: {integrity: sha512-4Qy2aK0LwH4eQiSvQWyKuC7JXE13bIopEQesWE0c/P3uuNRnZCQanI0vsrMLmUQJLAto+A+/8+sve2hd+BQuOQ==}
engines: {node: '>=18.17.0'}
@@ -4369,6 +4411,18 @@ packages:
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+ 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-refresh@0.14.2:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'}
@@ -4403,6 +4457,11 @@ packages:
'@types/react':
optional: true
+ react-timeago@7.2.0:
+ resolution: {integrity: sha512-2KsBEEs+qRhKx/kekUVNSTIpop3Jwd7SRBm0R4Eiq3mPeswRGSsftY9FpKsE/lXLdURyQFiHeHFrIUxLYskG5g==}
+ peerDependencies:
+ react: ^16.0.0 || ^17.0.0 || ^18.0.0
+
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@@ -4433,6 +4492,14 @@ packages:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
+ 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'}
@@ -4486,6 +4553,9 @@ packages:
resolution: {integrity: sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==}
engines: {node: '>=0.10.5'}
+ 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'}
@@ -5080,6 +5150,11 @@ packages:
'@types/react':
optional: true
+ 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==}
@@ -6675,6 +6750,16 @@ snapshots:
'@radix-ui/rect@1.1.0': {}
+ '@reduxjs/toolkit@2.2.7(react-redux@9.1.2(@types/react@18.3.5)(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.5)(react@18.3.1)(redux@5.0.1)
+
'@rushstack/eslint-patch@1.10.4': {}
'@sinclair/typebox@0.27.8': {}
@@ -7242,6 +7327,10 @@ snapshots:
dependencies:
'@types/react': 18.3.5
+ '@types/react-timeago@4.1.7':
+ dependencies:
+ '@types/react': 18.3.5
+
'@types/react@18.3.5':
dependencies:
'@types/prop-types': 15.7.12
@@ -7264,6 +7353,8 @@ snapshots:
'@types/unist@3.0.3': {}
+ '@types/use-sync-external-store@0.0.3': {}
+
'@types/uuid@9.0.8': {}
'@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4)':
@@ -9137,6 +9228,8 @@ snapshots:
dependencies:
queue: 6.0.2
+ immer@10.1.1: {}
+
import-fresh@3.3.0:
dependencies:
parent-module: 1.0.1
@@ -9618,6 +9711,12 @@ snapshots:
neo-async@2.6.2: {}
+ next-redux-wrapper@8.1.0(next@14.2.7(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-redux@9.1.2(@types/react@18.3.5)(react@18.3.1)(redux@5.0.1))(react@18.3.1):
+ dependencies:
+ next: 14.2.7(@babel/core@7.25.2)(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.5)(react@18.3.1)(redux@5.0.1)
+
next@14.2.7(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 14.2.7
@@ -10161,6 +10260,15 @@ snapshots:
react-is@18.3.1: {}
+ react-redux@9.1.2(@types/react@18.3.5)(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.5
+ redux: 5.0.1
+
react-refresh@0.14.2: {}
react-remove-scroll-bar@2.3.6(@types/react@18.3.5)(react@18.3.1):
@@ -10191,6 +10299,10 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.5
+ react-timeago@7.2.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
react@18.3.1:
dependencies:
loose-envify: 1.4.0
@@ -10240,6 +10352,12 @@ snapshots:
indent-string: 4.0.0
strip-indent: 3.0.0
+ 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
@@ -10315,6 +10433,8 @@ snapshots:
requireindex@1.2.0: {}
+ reselect@5.1.1: {}
+
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@@ -11002,6 +11122,10 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.5
+ use-sync-external-store@1.2.2(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
util-deprecate@1.0.2: {}
util@0.12.5:
diff --git a/src/components/post.tsx b/src/components/post.tsx
new file mode 100644
index 0000000..ad58eb5
--- /dev/null
+++ b/src/components/post.tsx
@@ -0,0 +1,22 @@
+import TimeAgo from 'react-timeago'
+
+type PostProps = {
+ images: Array<{ url: string }>
+ description: string
+ userName: string
+ createdAt: string
+}
+
+export function Post(post: PostProps) {
+ return (
+
+

+
{post.userName}
+
+
+ )
+}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 2065577..8dc3638 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -2,7 +2,29 @@ import '@fontsource/inter/400.css'
import '@fontsource/inter/700.css'
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
+import { wrapper } from '../services/store'
+import { Provider } from 'react-redux'
+import { PropsWithChildren } from 'react'
+import { useMeQuery } from '@/services/instagram.api'
-export default function App({ Component, pageProps }: AppProps) {
- return
+export default function App({ Component, ...rest }: AppProps) {
+ const { store, props } = wrapper.useWrappedStore(rest)
+ return (
+
+
+
+ )
+}
+
+function Me({ children }: PropsWithChildren) {
+ const { data, isLoading, isError } = useMeQuery()
+ console.log(data)
+ if (isLoading) {
+ return (
+
+
LOADING
+
+ )
+ }
+ return <>{children}>
}
diff --git a/src/pages/auth/login.tsx b/src/pages/auth/login.tsx
index b3340e5..024f354 100644
--- a/src/pages/auth/login.tsx
+++ b/src/pages/auth/login.tsx
@@ -2,6 +2,8 @@ import { useForm } from 'react-hook-form'
import { z } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'
import { TextField } from '@/components/ui/text-field/text-field'
+import { useLoginMutation } from '@/services/instagram.api'
+import { useRouter } from 'next/router'
const loginSchema = z.object({
email: z
@@ -14,21 +16,11 @@ const loginSchema = z.object({
.min(3, 'Минимум 3 символа'),
})
-const createLoginSchema = (t: ReturnType) => {
- return z.object({
- email: z.string().min(1, 'Required').email(t('ERROR_INVALID_EMAIL')),
- password: z
- .string({ required_error: 'Required' })
- .min(1, 'Required')
- .min(3, 'Минимум 3 символа'),
- })
-}
-
type LoginFields = z.infer
export default function Login() {
- const t = useI18n()
-
+ const [logIn, { data, isLoading, isError }] = useLoginMutation()
+ const router = useRouter()
const {
handleSubmit,
register,
@@ -36,9 +28,15 @@ export default function Login() {
} = useForm({
resolver: zodResolver(loginSchema),
})
+ console.log({ data, isError })
const onSubmit = handleSubmit((data) => {
- console.log(data)
+ logIn(data)
+ .unwrap()
+ .then((data) => {
+ localStorage.setItem('access_token', data.accessToken)
+ router.push('/')
+ })
})
return (
@@ -61,25 +59,8 @@ export default function Login() {
{...register('password')}
/>
-
+
)
}
-
-const useI18n = () => {
- const lang = 'ru'
-
- return (key: keyof (typeof translations)['ru']) => {
- return translations[lang][key]
- }
-}
-
-const translations = {
- ru: {
- ERROR_INVALID_EMAIL: 'Введите валидный адрес эл. почты',
- },
- en: {
- ERROR_INVALID_EMAIL: 'Invalid email',
- },
-} as const
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index ef6ad47..50f28cc 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,11 +1,58 @@
import Link from 'next/link'
+import { useGetAllPublicPostsQuery } from '@/services/instagram.api'
+import { useState } from 'react'
+import TimeAgo from 'react-timeago'
+import { Post } from '@/components/post'
export default function Home() {
+ const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc')
+ const { data, isLoading, isError } = useGetAllPublicPostsQuery({
+ pageSize: 4,
+ sortBy: 'userName',
+ sortDirection: sortDirection,
+ })
+
+ if (isLoading) {
+ return Loading...
+ }
+
+ if (isError) {
+ return Error!
+ }
+
+ if (!data) {
+ return null
+ }
+
return (
-
-
auth
-
login
-
sign-up
+
+
+ auth
+ login
+ sign-up
+
+
Registered Users: {data.totalUsers}
+
+
+
+ {data.items.map((post) => {
+ return (
+ -
+
+
+ )
+ })}
+
+
)
}
diff --git a/src/pages/profile/[id].tsx b/src/pages/profile/[id].tsx
new file mode 100644
index 0000000..4f40241
--- /dev/null
+++ b/src/pages/profile/[id].tsx
@@ -0,0 +1,110 @@
+import { useRouter } from 'next/router'
+import {
+ useCreatePostMutation,
+ useGetUserPostsQuery,
+ useGetUserProfileQuery,
+ useUploadFileForPostMutation,
+} from '@/services/instagram.api'
+import { Post } from '@/components/post'
+import { useState } from 'react'
+
+export default function UserProfile() {
+ const [photoToUpload, setPhotoToUpload] = useState
(null)
+ const [uploadPhoto, { data }] = useUploadFileForPostMutation()
+ const [createPost] = useCreatePostMutation()
+
+ console.log(photoToUpload)
+ const route = useRouter()
+ const userId = route.query.id
+
+ const { data: userProfile, isLoading: isUserProfileLoading } =
+ useGetUserProfileQuery(
+ {
+ id: parseInt(userId as string, 10),
+ },
+ {
+ skip: userId === undefined,
+ }
+ )
+ const { data: userPosts, isLoading: isUserPostsLoading } =
+ useGetUserPostsQuery(
+ {
+ id: parseInt(userId as string, 10),
+ },
+ {
+ skip: userId === undefined,
+ }
+ )
+
+ if (isUserPostsLoading || isUserProfileLoading) {
+ return Loading
+ }
+
+ const avatarUrl = userProfile?.avatars?.[0]?.url
+
+ return (
+
+
+
+
+ {data && (
+
+
Create post
+
+ {data.images.map((img) => (
+

+ ))}
+
+
+
+ )}
+
{userProfile?.userName}
+
{userProfile?.aboutMe}
+ {avatarUrl &&

}
+
+ {userPosts?.items.map((post) => {
+ return (
+ -
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/src/services/instagram.api.ts b/src/services/instagram.api.ts
new file mode 100644
index 0000000..2538850
--- /dev/null
+++ b/src/services/instagram.api.ts
@@ -0,0 +1,102 @@
+// Need to use the React-specific entry point to import createApi
+import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
+import {
+ GetAllPostsArgs,
+ GetAllPostsResponse,
+ LoginArgs,
+ LoginResponse,
+ MeResponse,
+ UploadFileResponse,
+ UserPosts,
+ UserProfile,
+} from '@/services/instagram.types'
+
+// Define a service using a base URL and expected endpoints
+export const instagramApi = createApi({
+ tagTypes: ['Posts'],
+ reducerPath: 'instagramApi',
+ baseQuery: fetchBaseQuery({
+ baseUrl: 'https://inctagram.work/api/',
+ prepareHeaders: (headers) => {
+ const token = localStorage.getItem('access_token')
+ if (token) {
+ headers.set('Authorization', `Bearer ${token}`)
+ }
+ return headers
+ },
+ }),
+ endpoints: (builder) => ({
+ getAllPublicPosts: builder.query<
+ GetAllPostsResponse,
+ GetAllPostsArgs | void
+ >({
+ query: (arg) => {
+ const { endCursorPostId, ...params } = arg ?? {}
+ return { url: `/v1/public-posts/all/${endCursorPostId}`, params }
+ },
+ }),
+ login: builder.mutation({
+ query: (args) => ({ url: 'v1/auth/login', body: args, method: 'POST' }),
+ }),
+ me: builder.query({
+ query: () => ({
+ url: '/v1/auth/me',
+ }),
+ }),
+ getUserProfile: builder.query({
+ query: ({ id }) => ({
+ url: `/v1/public-user/profile/${id}`,
+ }),
+ }),
+ getUserPosts: builder.query({
+ providesTags: ['Posts'],
+ query: ({ id }) => ({
+ url: `/v1/public-posts/user/${id}`,
+ }),
+ }),
+ uploadFileForPost: builder.mutation({
+ query: ({ file }) => {
+ const formData = new FormData()
+ formData.append('file', file)
+ return {
+ url: '/v1/posts/image',
+ body: formData,
+ method: 'POST',
+ }
+ },
+ }),
+ createPost: builder.mutation<
+ any,
+ { description: string; uploadIds: string[] }
+ >({
+ invalidatesTags: ['Posts'],
+ query: ({ description, uploadIds }) => {
+ return {
+ url: '/v1/posts',
+ body: {
+ description,
+ childrenMetadata: uploadIds.map((id) => {
+ return {
+ uploadId: id,
+ }
+ }),
+ },
+
+ method: 'POST',
+ }
+ },
+ }),
+ }),
+})
+
+// Export hooks for usage in functional components, which are
+// auto-generated based on the defined endpoints
+export const {
+ useGetAllPublicPostsQuery,
+ useLoginMutation,
+ useMeQuery,
+ useGetUserProfileQuery,
+ useGetUserPostsQuery,
+ useUploadFileForPostMutation,
+ useCreatePostMutation,
+} = instagramApi
diff --git a/src/services/instagram.types.ts b/src/services/instagram.types.ts
new file mode 100644
index 0000000..d29c31d
--- /dev/null
+++ b/src/services/instagram.types.ts
@@ -0,0 +1,91 @@
+export interface UserProfile {
+ id: number
+ userName: string
+ firstName: string
+ lastName: string
+ city: string
+ country: string
+ region: string
+ dateOfBirth: string
+ aboutMe: string
+ avatars: Avatar[]
+ createdAt: string
+}
+
+export interface Avatar {
+ url: string
+ width: number
+ height: number
+ fileSize: number
+ createdAt: string
+}
+
+export interface MeResponse {
+ userId: number
+ userName: string
+ email: string
+ isBlocked: boolean
+}
+
+export interface LoginArgs {
+ email: string
+ password: string
+}
+
+export interface LoginResponse {
+ accessToken: string
+}
+
+export interface GetAllPostsArgs {
+ pageSize?: number
+ sortBy?: string
+ sortDirection?: 'asc' | 'desc'
+ endCursorPostId?: number
+}
+
+export interface GetAllPostsResponse {
+ totalCount: number
+ pageSize: number
+ items: Post[]
+ totalUsers: number
+}
+
+export interface Post {
+ id: number
+ userName: string
+ description: string
+ location?: string | null
+ 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 UserPosts {
+ totalCount: number
+ pageSize: number
+ items: Post[]
+ totalUsers: number
+}
+
+export interface UploadFileResponse {
+ images: Image[]
+}
diff --git a/src/services/store.ts b/src/services/store.ts
new file mode 100644
index 0000000..ad34b22
--- /dev/null
+++ b/src/services/store.ts
@@ -0,0 +1,33 @@
+import {
+ Action,
+ combineSlices,
+ configureStore,
+ ThunkAction,
+} from '@reduxjs/toolkit'
+import { createWrapper } from 'next-redux-wrapper'
+import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
+import { instagramApi } from '@/services/instagram.api'
+
+const makeStore = () =>
+ configureStore({
+ reducer: combineSlices(instagramApi),
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware().concat(instagramApi.middleware),
+ devTools: true,
+ })
+
+export type AppStore = ReturnType
+export type AppState = ReturnType
+export type AppDispatch = AppStore['dispatch']
+export type AppThunk = ThunkAction<
+ ReturnType,
+ AppState,
+ unknown,
+ Action
+>
+
+// Use throughout your app instead of plain `useDispatch` and `useSelector`
+export const useAppDispatch = () => useDispatch()
+export const useAppSelector: TypedUseSelectorHook = useSelector
+
+export const wrapper = createWrapper(makeStore)