redux setup
This commit is contained in:
parent
62617bfb6f
commit
e82b06b508
69
cool_todo_manager/package-lock.json
generated
69
cool_todo_manager/package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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}>
|
||||
<Provider store={store}>
|
||||
{/* <ThemePanel /> */}
|
||||
<RouterProvider router={router} />
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Theme>
|
||||
);
|
||||
|
||||
16
cool_todo_manager/src/api/RTKQuery.ts
Normal file
16
cool_todo_manager/src/api/RTKQuery.ts
Normal 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);
|
||||
15
cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx
Normal file
15
cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@ -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}
|
||||
|
||||
@ -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' />
|
||||
<CardGroup id="qwhdf" />
|
||||
<CardGroup id="123fsduiyuiyi" />
|
||||
</DragDropContext>
|
||||
<Button onClick={onClick}>asdasdasd </Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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,12 +51,18 @@ 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"
|
||||
|
||||
145
cool_todo_manager/src/services/mainApi.ts
Normal file
145
cool_todo_manager/src/services/mainApi.ts
Normal 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;
|
||||
Loading…
x
Reference in New Issue
Block a user