redux setup

This commit is contained in:
Max 2025-03-01 21:32:57 +03:00
parent 62617bfb6f
commit e82b06b508
9 changed files with 320 additions and 21 deletions

View File

@ -12,18 +12,22 @@
"@radix-ui/react-form": "^0.1.2",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/themes": "^3.2.0",
"@reduxjs/toolkit": "^2.6.0",
"@tailwindcss/vite": "^4.0.6",
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-devtools": "^5.66.0",
"axios": "^1.7.9",
"jotai": "^2.12.0",
"js-cookie": "^3.0.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-redux": "^9.2.0",
"react-router-dom": "^6.26.2",
"tailwindcss": "^4.0.6"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@types/js-cookie": "^3.0.6",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",
@ -2547,6 +2551,30 @@
}
}
},
"node_modules/@reduxjs/toolkit": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz",
"integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==",
"license": "MIT",
"dependencies": {
"immer": "^10.0.3",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"reselect": "^5.1.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@remix-run/router": {
"version": "1.19.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
@ -3131,6 +3159,13 @@
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"license": "MIT"
},
"node_modules/@types/js-cookie": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz",
"integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@ -4423,6 +4458,16 @@
"node": ">= 4"
}
},
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@ -4520,6 +4565,15 @@
}
}
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -5394,12 +5448,27 @@
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
"license": "MIT",
"peerDependencies": {
"redux": "^5.0.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/reselect": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
"license": "MIT"
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",

View File

@ -14,18 +14,22 @@
"@radix-ui/react-form": "^0.1.2",
"@radix-ui/react-icons": "^1.3.2",
"@radix-ui/themes": "^3.2.0",
"@reduxjs/toolkit": "^2.6.0",
"@tailwindcss/vite": "^4.0.6",
"@tanstack/react-query": "^5.66.0",
"@tanstack/react-query-devtools": "^5.66.0",
"axios": "^1.7.9",
"jotai": "^2.12.0",
"js-cookie": "^3.0.5",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-redux": "^9.2.0",
"react-router-dom": "^6.26.2",
"tailwindcss": "^4.0.6"
},
"devDependencies": {
"@eslint/js": "^9.19.0",
"@types/js-cookie": "^3.0.6",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@vitejs/plugin-react": "^4.3.4",

View File

@ -1,8 +1,10 @@
import { Theme } from '@radix-ui/themes';
import '@radix-ui/themes/styles.css';
import { QueryClientProvider } from '@tanstack/react-query';
import { Provider } from 'react-redux';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import queryClient from './api/queryClient';
import { store } from './api/RTKQuery';
import './App.css';
import MyRoutes from './routes/routes';
@ -10,10 +12,12 @@ const router = createBrowserRouter(MyRoutes);
function App() {
return (
<Theme className='size-full' accentColor="amber" grayColor="gray">
<Theme className="size-full" accentColor="amber" grayColor="gray">
<QueryClientProvider client={queryClient}>
{/* <ThemePanel /> */}
<RouterProvider router={router} />
<Provider store={store}>
{/* <ThemePanel /> */}
<RouterProvider router={router} />
</Provider>
</QueryClientProvider>
</Theme>
);

View File

@ -0,0 +1,16 @@
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { authApi, mainApi } from '../services/mainApi';
export const store = configureStore({
reducer: {
[mainApi.reducerPath]: mainApi.reducer,
[authApi.reducerPath]: authApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.concat(mainApi.middleware)
.concat(authApi.middleware),
});
setupListeners(store.dispatch);

View File

@ -0,0 +1,15 @@
import { useNavigate } from "react-router-dom"
export default function AuthWrapper() {
const navigate = useNavigate()
if(!localStorage.getItem('token')) {
navigate('/login')
}
return (
<div>
</div>
)
}

View File

@ -1,5 +1,6 @@
import { Droppable } from '@hello-pangea/dnd';
import { Box } from '@radix-ui/themes';
import { useState } from 'react';
import TaskCard from '../TaskCard/TaskCard';
const tasks = [
@ -12,11 +13,13 @@ type TCardGroup = {
};
export default function CardGroup(props: TCardGroup) {
const [localTasks, setLocalTasks] = useState(tasks)
return (
<Droppable droppableId={props.id}>
{(provided) => (
<Box ref={provided.innerRef} {...provided.droppableProps}>
{tasks.map((task, i) => (
{localTasks.map((task, i) => (
<TaskCard
key={task.id}
id={task.id.toString() + props.id}

View File

@ -1,17 +1,36 @@
import { DragDropContext } from '@hello-pangea/dnd';
import { Button } from '@radix-ui/themes';
import {
useCreateTaskMutation,
useGetTasksQuery,
} from '../../services/mainApi';
import CardGroup from '../CardGroup/CardGroup';
export default function MainBoard() {
const dragEndHandle = (result: TDragResult) => {
result;
};
const dragEndHandle = (result: TDragResult ) => {
result
}
const { data } = useGetTasksQuery();
const [create] = useCreateTaskMutation();
const onClick = () => {
create({
title: 'My Task 123',
projectId: 1,
assignedUserId: 2,
deadline: '2025-03-01T12:00:00Z',
});
};
return (
<DragDropContext onDragEnd={dragEndHandle}>
<CardGroup id='qwhdf' />
<CardGroup id='123fsduiyuiyi' />
</DragDropContext>
<>
<DragDropContext onDragEnd={dragEndHandle}>
<CardGroup id="qwhdf" />
<CardGroup id="123fsduiyuiyi" />
</DragDropContext>
<Button onClick={onClick}>asdasdasd </Button>
</>
);
}

View File

@ -1,24 +1,42 @@
import { Button, Card, Heading, Text, TextField } from '@radix-ui/themes';
import { Form } from 'radix-ui';
import { useState } from 'react';
import { useLoginMutation } from '../../../services/mainApi';
export default function LoginPage() {
const [login, { isError }] = useLoginMutation();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const submitHandler = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
login({ username, password });
};
return (
<>
<Card className="absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 w-fit">
<Heading size="4" className='text-center !mb-2'>Login</Heading>
<Heading size="4" className="text-center !mb-2">
Login
</Heading>
<Form.Root className="flex flex-col gap-4" onSubmit={e => e.preventDefault()}>
<Form.Root
className="flex flex-col gap-4"
onSubmit={submitHandler}
>
<Form.Field name="email">
<Form.Message match={'valueMissing'}>
<Text>Email is required</Text>
</Form.Message>
<Form.Message match={'typeMismatch'}>
<Text>Email is not valid</Text>
<Text>Username is required</Text>
</Form.Message>
<Form.Control asChild>
<TextField.Root type="email" placeholder="Email">
<TextField.Root
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
>
<TextField.Slot />
</TextField.Root>
</Form.Control>
@ -33,16 +51,22 @@ export default function LoginPage() {
<TextField.Root
type="password"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
>
<TextField.Slot />
</TextField.Root>
</Form.Control>
</Form.Field>
{isError && (
<Text>{'Unable to login. Please try again.'}</Text>
)}
<Button
// onClick={(e) => e.preventDefault()}
className="mt-4"
>
>
Sign In
</Button>
</Form.Root>

View File

@ -0,0 +1,145 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const mainApi = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: 'http://109.107.166.17:3000/api/',
prepareHeaders: (headers) => {
headers.set(
'Authorization',
`Bearer ${localStorage.getItem('token')}`,
);
return headers;
},
}),
tagTypes: ['Task', 'Project'],
endpoints: (builder) => ({
getTasks: builder.query<string[], void>({
query: () => ({
url: 'tasks',
method: 'GET',
}),
providesTags: (result) =>
result
? [
...result.map((task) => ({
type: 'Task' as const,
id: task,
})),
{ type: 'Task', id: 'LIST' },
]
: [{ type: 'Task', id: 'LIST' }],
}),
getTask: builder.query<string, string>({
query: (id) => ({
url: `tasks/${id}`,
method: 'GET',
}),
providesTags: (result, error, id) => [{ type: 'Task', id }],
}),
updateTask: builder.mutation({
query: (task) => ({
url: `tasks/${task.id}`,
method: 'PATCH',
body: task,
}),
invalidatesTags: (result, error, id) => [{ type: 'Task', id }],
}),
deleteTask: builder.mutation({
query: (id) => ({
url: `tasks/${id}`,
method: 'DELETE',
}),
invalidatesTags: (result, error, id) => [{ type: 'Task', id }],
}),
createTask: builder.mutation({
query: (newTask) => ({
url: 'tasks/create',
method: 'POST',
body: newTask,
}),
invalidatesTags: [{ type: 'Task', id: 'LIST' }],
}),
// PROJECTS
createProject: builder.mutation({
query: (newProject) => ({
url: 'tasks/create',
method: 'POST',
body: newProject,
}),
invalidatesTags: [{ type: 'Project', id: 'LIST' }],
}),
getProjects: builder.query({
query: () => ({
url: 'projects',
method: 'GET',
}),
providesTags: (result, error, id) => [
{ type: 'Project', id: 'LIST' },
],
}),
getProject: builder.query({
query: (id) => ({
url: `projects/${id}`,
method: 'GET',
}),
providesTags: (result, error, id) => [{ type: 'Project', id }],
}),
updateProject: builder.mutation({
query: ({ id, project }) => ({
url: `projects/${id}`,
method: 'PATCH',
body: project,
}),
invalidatesTags: (result, error, id) => [{ type: 'Project', id }],
}),
deleteProject: builder.mutation({
query: (id) => ({
url: `projects/${id}`,
method: 'DELETE',
}),
invalidatesTags: (result, error, id) => [{ type: 'Project', id }],
}),
}),
});
export const authApi = createApi({
reducerPath: 'authApi',
baseQuery: fetchBaseQuery({
baseUrl: 'http://109.107.166.17:3000/api/auth/',
}),
endpoints: (builder) => ({
login: builder.mutation({
query: (credentials) => ({
url: 'login',
method: 'POST',
body: credentials,
}),
async onQueryStarted(arg, { queryFulfilled }) {
try {
const response = await queryFulfilled;
if (response.data) {
localStorage.setItem(
'token',
response.data.access_token,
);
window.location.href = '/';
}
} catch (error) {}
},
}),
register: builder.mutation({
query: (credentials) => ({
url: 'register',
method: 'POST',
body: credentials,
}),
}),
}),
});
export const { useLoginMutation, useRegisterMutation } = authApi;
export const { useGetTasksQuery, useCreateTaskMutation, useUpdateTaskMutation, useDeleteTaskMutation } = mainApi;
export const { useGetProjectsQuery, useCreateProjectMutation, useUpdateProjectMutation, useDeleteProjectMutation } = mainApi;