redux setup
This commit is contained in:
parent
e82b06b508
commit
cabb8a9b27
@ -1,7 +1,22 @@
|
|||||||
import { configureStore } from '@reduxjs/toolkit';
|
import { configureStore, isRejectedWithValue, Middleware } from '@reduxjs/toolkit';
|
||||||
import { setupListeners } from '@reduxjs/toolkit/query';
|
import { setupListeners } from '@reduxjs/toolkit/query';
|
||||||
import { authApi, mainApi } from '../services/mainApi';
|
import { authApi, mainApi } from '../services/mainApi';
|
||||||
|
|
||||||
|
const loggerMiddleware: Middleware = (_store) => (next) => (action) => {
|
||||||
|
console.log('dispatching', action);
|
||||||
|
if(isRejectedWithValue(action)) {
|
||||||
|
// @ts-ignore
|
||||||
|
const statusCode = action.payload.status;
|
||||||
|
if(statusCode === 401) {
|
||||||
|
console.log('Unauthorized, redirecting to login page');
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
window.location.href = '/login';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = next(action);
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
export const store = configureStore({
|
export const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
[mainApi.reducerPath]: mainApi.reducer,
|
[mainApi.reducerPath]: mainApi.reducer,
|
||||||
@ -9,8 +24,10 @@ export const store = configureStore({
|
|||||||
},
|
},
|
||||||
middleware: (getDefaultMiddleware) =>
|
middleware: (getDefaultMiddleware) =>
|
||||||
getDefaultMiddleware()
|
getDefaultMiddleware()
|
||||||
|
.prepend(loggerMiddleware)
|
||||||
.concat(mainApi.middleware)
|
.concat(mainApi.middleware)
|
||||||
.concat(authApi.middleware),
|
.concat(authApi.middleware),
|
||||||
});
|
});
|
||||||
|
|
||||||
setupListeners(store.dispatch);
|
setupListeners(store.dispatch);
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
import { useNavigate } from "react-router-dom"
|
import { PropsWithChildren } from 'react';
|
||||||
|
import { Navigate } from 'react-router-dom';
|
||||||
|
|
||||||
export default function AuthWrapper() {
|
export default function AuthWrapper(props: PropsWithChildren) {
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
if(!localStorage.getItem('token')) {
|
if (!localStorage.getItem('token')) {
|
||||||
navigate('/login')
|
console.log('No token found, redirecting to login');
|
||||||
|
return <Navigate to={'/login'} />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <div>{props.children}</div>;
|
||||||
<div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,34 +1,69 @@
|
|||||||
import { Droppable } from '@hello-pangea/dnd';
|
import { Droppable } from '@hello-pangea/dnd';
|
||||||
import { Box } from '@radix-ui/themes';
|
import { Box, Button, Flex, Text } from '@radix-ui/themes';
|
||||||
import { useState } from 'react';
|
import {
|
||||||
|
useCreateTaskMutation,
|
||||||
|
useDeleteProjectMutation,
|
||||||
|
useGetTasksForGroupQuery,
|
||||||
|
} from '../../services/mainApi';
|
||||||
import TaskCard from '../TaskCard/TaskCard';
|
import TaskCard from '../TaskCard/TaskCard';
|
||||||
|
import CreateTaskDialog from './CreateTaskDialog/CreateTaskDialog';
|
||||||
const tasks = [
|
import DeleteProjectDialog from './DeleteProjectDialog/CreateTaskDialog';
|
||||||
{ id: 1, title: 'Task 1', description: 'Description for Task 1' },
|
|
||||||
{ id: 2, title: 'Task 2', description: 'Description for Task 2' },
|
|
||||||
];
|
|
||||||
|
|
||||||
type TCardGroup = {
|
type TCardGroup = {
|
||||||
id: string;
|
id: string;
|
||||||
|
title: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function CardGroup(props: TCardGroup) {
|
export default function CardGroup(props: TCardGroup) {
|
||||||
const [localTasks, setLocalTasks] = useState(tasks)
|
const { data, isLoading } = useGetTasksForGroupQuery(props.id);
|
||||||
|
|
||||||
|
const [createTaskForGroup] = useCreateTaskMutation();
|
||||||
|
const [deleteProject] = useDeleteProjectMutation();
|
||||||
|
|
||||||
|
const createTask = (taskText: string, date: string) => {
|
||||||
|
createTaskForGroup({
|
||||||
|
title: taskText,
|
||||||
|
projectId: props.id,
|
||||||
|
assignedUserId: 1,
|
||||||
|
deadline: date,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteGroup = () => {
|
||||||
|
deleteProject(props.id);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Droppable droppableId={props.id}>
|
<Droppable droppableId={props.id.toString()}>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<Box ref={provided.innerRef} {...provided.droppableProps}>
|
<Box className="flex flex-col text-center rounded-lg p-4 bg-gray-200 min-w-fit">
|
||||||
{localTasks.map((task, i) => (
|
<Text className="mb-2">{props.title}</Text>
|
||||||
|
<Box
|
||||||
|
ref={provided.innerRef}
|
||||||
|
className="bg-gray-200 min-w-full rounded-lg !flex flex-col gap-2"
|
||||||
|
>
|
||||||
|
{data &&
|
||||||
|
data.map((task, i) => (
|
||||||
<TaskCard
|
<TaskCard
|
||||||
key={task.id}
|
key={task.id}
|
||||||
id={task.id.toString() + props.id}
|
id={task.id.toString() + props.id}
|
||||||
title={task.title}
|
title={task.title}
|
||||||
description={task.description}
|
description={task.status}
|
||||||
index={i}
|
index={i}
|
||||||
|
status={task.status}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{provided.placeholder}
|
</Box>
|
||||||
|
<Flex gap={'2'} className="mx-auto w-full mt-2">
|
||||||
|
<CreateTaskDialog onClose={createTask}>
|
||||||
|
<Button>Add Task</Button>
|
||||||
|
</CreateTaskDialog>
|
||||||
|
<DeleteProjectDialog onClose={deleteGroup}>
|
||||||
|
<Button color="red" onClick={deleteGroup}>
|
||||||
|
Delete Project
|
||||||
|
</Button>
|
||||||
|
</DeleteProjectDialog>
|
||||||
|
</Flex>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</Droppable>
|
</Droppable>
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
import { Button, Dialog, Flex, Text, TextField } from '@radix-ui/themes';
|
||||||
|
import { PropsWithChildren, useState } from 'react';
|
||||||
|
|
||||||
|
type TCreateTaskDialog = {
|
||||||
|
onClose: (text: string, timeString: string) => void;
|
||||||
|
} & PropsWithChildren;
|
||||||
|
|
||||||
|
export default function CreateTaskDialog(props: TCreateTaskDialog) {
|
||||||
|
const [text, setText] = useState('');
|
||||||
|
const [deadline, setDeadline] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog.Root>
|
||||||
|
<Dialog.Trigger>{props.children}</Dialog.Trigger>
|
||||||
|
|
||||||
|
<Dialog.Content maxWidth="450px">
|
||||||
|
<Dialog.Title>Task creation</Dialog.Title>
|
||||||
|
|
||||||
|
<Flex direction="column" gap="3">
|
||||||
|
<label>
|
||||||
|
<Text as="div" size="2" mb="1" weight="bold">
|
||||||
|
Task description
|
||||||
|
</Text>
|
||||||
|
<TextField.Root
|
||||||
|
defaultValue=""
|
||||||
|
value={text}
|
||||||
|
onChange={(e) => setText(e.target.value)}
|
||||||
|
placeholder="Enter task description"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<Text as="div" size="2" mb="1" weight="bold">
|
||||||
|
Deadline
|
||||||
|
</Text>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
onChange={(e) => setDeadline(e.target.value)}
|
||||||
|
></input>
|
||||||
|
</label>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Flex gap="3" mt="4" justify="end">
|
||||||
|
<Dialog.Close>
|
||||||
|
<Button variant="soft" color="gray">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Dialog.Close>
|
||||||
|
<Dialog.Close>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
props.onClose(text, deadline);
|
||||||
|
setDeadline('');
|
||||||
|
setText('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</Dialog.Close>
|
||||||
|
</Flex>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
import { Button, Dialog, Flex } from '@radix-ui/themes';
|
||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
|
||||||
|
type TCreateTaskDialog = {
|
||||||
|
onClose: () => void;
|
||||||
|
} & PropsWithChildren;
|
||||||
|
|
||||||
|
export default function DeleteProjectDialog(props: TCreateTaskDialog) {
|
||||||
|
return (
|
||||||
|
<Dialog.Root>
|
||||||
|
<Dialog.Trigger>{props.children}</Dialog.Trigger>
|
||||||
|
|
||||||
|
<Dialog.Content maxWidth="450px">
|
||||||
|
<Dialog.Title>Delete project?</Dialog.Title>
|
||||||
|
|
||||||
|
<Flex gap="3" mt="4" justify="end">
|
||||||
|
<Dialog.Close>
|
||||||
|
<Button variant="soft" color="gray">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Dialog.Close>
|
||||||
|
<Dialog.Close>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
props.onClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
</Dialog.Close>
|
||||||
|
</Flex>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { DragDropContext } from '@hello-pangea/dnd';
|
import { DragDropContext } from '@hello-pangea/dnd';
|
||||||
import { Button } from '@radix-ui/themes';
|
import { Button, Flex, ScrollArea } from '@radix-ui/themes';
|
||||||
import {
|
import {
|
||||||
|
useCreateProjectMutation,
|
||||||
useCreateTaskMutation,
|
useCreateTaskMutation,
|
||||||
useGetTasksQuery,
|
useGetProjectsQuery,
|
||||||
} from '../../services/mainApi';
|
} from '../../services/mainApi';
|
||||||
import CardGroup from '../CardGroup/CardGroup';
|
import CardGroup from '../CardGroup/CardGroup';
|
||||||
|
|
||||||
@ -11,26 +12,40 @@ export default function MainBoard() {
|
|||||||
result;
|
result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { data } = useGetTasksQuery();
|
|
||||||
|
|
||||||
const [create] = useCreateTaskMutation();
|
const [create] = useCreateTaskMutation();
|
||||||
|
const [createProject] = useCreateProjectMutation();
|
||||||
|
const { data: cringe, isLoading } = useGetProjectsQuery({});
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = () => {
|
||||||
create({
|
create({
|
||||||
title: 'My Task 123',
|
title: 'My Task 123',
|
||||||
projectId: 1,
|
projectId: 4,
|
||||||
assignedUserId: 2,
|
assignedUserId: 1,
|
||||||
deadline: '2025-03-01T12:00:00Z',
|
deadline: '2025-03-01T12:00:00Z',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onClick1 = () => {
|
||||||
|
createProject({
|
||||||
|
title: 'My project test 12',
|
||||||
|
description: 'Test desc 123 123 123',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DragDropContext onDragEnd={dragEndHandle}>
|
<DragDropContext onDragEnd={dragEndHandle}>
|
||||||
<CardGroup id="qwhdf" />
|
<ScrollArea scrollbars='horizontal'>
|
||||||
<CardGroup id="123fsduiyuiyi" />
|
<Flex gap={'2'} className='min-w-fit'>
|
||||||
|
{!isLoading &&
|
||||||
|
(cringe as any[]).map((item: any) => (
|
||||||
|
<CardGroup id={item.id} title={item.title} />
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
</ScrollArea>
|
||||||
</DragDropContext>
|
</DragDropContext>
|
||||||
<Button onClick={onClick}>asdasdasd </Button>
|
<Button onClick={onClick}>Create task</Button>
|
||||||
|
<Button onClick={onClick1}>Create project</Button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { Draggable } from '@hello-pangea/dnd';
|
import { Draggable } from '@hello-pangea/dnd';
|
||||||
import { DragHandleHorizontalIcon } from '@radix-ui/react-icons';
|
import { Button, Card, Flex, Text } from '@radix-ui/themes';
|
||||||
import { Box, Button, Card, Flex, Text } from '@radix-ui/themes';
|
|
||||||
|
|
||||||
type TTaskCard = {
|
type TTaskCard = {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
index?: number;
|
index?: number;
|
||||||
|
status: "todo" | "in-progress" | "completed";
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TaskCard(props: TTaskCard) {
|
export default function TaskCard(props: TTaskCard) {
|
||||||
@ -21,14 +21,6 @@ export default function TaskCard(props: TTaskCard) {
|
|||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
<Text wrap="pretty">{props.title}</Text>
|
<Text wrap="pretty">{props.title}</Text>
|
||||||
<Button>Mark completed</Button>
|
<Button>Mark completed</Button>
|
||||||
<Flex className="w-full !justify-center transition-transform">
|
|
||||||
<Box
|
|
||||||
className="px-10 cursor-grab"
|
|
||||||
{...provided.dragHandleProps}
|
|
||||||
>
|
|
||||||
<DragHandleHorizontalIcon />
|
|
||||||
</Box>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
import { Button, Card, Heading, Text, TextField } from '@radix-ui/themes';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Heading,
|
||||||
|
Link,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
} from '@radix-ui/themes';
|
||||||
import { Form } from 'radix-ui';
|
import { Form } from 'radix-ui';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useLoginMutation } from '../../../services/mainApi';
|
import { useLoginMutation } from '../../../services/mainApi';
|
||||||
@ -69,6 +77,12 @@ export default function LoginPage() {
|
|||||||
>
|
>
|
||||||
Sign In
|
Sign In
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Box className='w-full !flex justify-center'>
|
||||||
|
<Link href="/register">
|
||||||
|
<Text>Register</Text>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
</Form.Root>
|
</Form.Root>
|
||||||
</Card>
|
</Card>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,11 +1,24 @@
|
|||||||
import { Button, Card, Heading, Text, TextField } from '@radix-ui/themes';
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Heading,
|
||||||
|
Link,
|
||||||
|
Text,
|
||||||
|
TextField,
|
||||||
|
} from '@radix-ui/themes';
|
||||||
import { Form } from 'radix-ui';
|
import { Form } from 'radix-ui';
|
||||||
|
|
||||||
export default function RegisterPage() {
|
export default function RegisterPage() {
|
||||||
return (
|
return (
|
||||||
<Card className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-fit">
|
<Card className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-fit">
|
||||||
<Heading size="4" className='text-center !mb-2'>Register</Heading>
|
<Heading size="4" className="text-center !mb-2">
|
||||||
<Form.Root className="flex flex-col gap-4" onSubmit={e => e.preventDefault()}>
|
Register
|
||||||
|
</Heading>
|
||||||
|
<Form.Root
|
||||||
|
className="flex flex-col gap-4"
|
||||||
|
onSubmit={(e) => e.preventDefault()}
|
||||||
|
>
|
||||||
<Form.Field name="email">
|
<Form.Field name="email">
|
||||||
<Form.Message match="valueMissing">
|
<Form.Message match="valueMissing">
|
||||||
<Text>Email is required</Text>
|
<Text>Email is required</Text>
|
||||||
@ -42,7 +55,15 @@ export default function RegisterPage() {
|
|||||||
</Form.Control>
|
</Form.Control>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
|
|
||||||
<Button onClick={e => e.preventDefault()} className="mt-4">Register</Button>
|
<Button onClick={(e) => e.preventDefault()} className="mt-4">
|
||||||
|
Register
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Box className="w-full !flex justify-center">
|
||||||
|
<Link href="/login">
|
||||||
|
<Text>Login</Text>
|
||||||
|
</Link>
|
||||||
|
</Box>
|
||||||
</Form.Root>
|
</Form.Root>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { createRoutesFromElements, Route } from 'react-router-dom';
|
import { createRoutesFromElements, Route } from 'react-router-dom';
|
||||||
|
import AuthWrapper from '../components/AuthWrapper/AuthWrapper';
|
||||||
import MainBoard from '../components/MainBoard/MainBoard';
|
import MainBoard from '../components/MainBoard/MainBoard';
|
||||||
import LoginPage from '../pages/auth/LoginPage/LoginPage';
|
import LoginPage from '../pages/auth/LoginPage/LoginPage';
|
||||||
import RegisterPage from '../pages/auth/RegisterPage/RegisterPage';
|
import RegisterPage from '../pages/auth/RegisterPage/RegisterPage';
|
||||||
@ -6,7 +7,14 @@ import MainPage from '../pages/main/MainPage';
|
|||||||
|
|
||||||
const MyRoutes = createRoutesFromElements(
|
const MyRoutes = createRoutesFromElements(
|
||||||
<>
|
<>
|
||||||
<Route path="/" element={<MainPage />}>
|
<Route
|
||||||
|
path="/"
|
||||||
|
element={
|
||||||
|
<AuthWrapper>
|
||||||
|
<MainPage />
|
||||||
|
</AuthWrapper>
|
||||||
|
}
|
||||||
|
>
|
||||||
<Route index element={<MainBoard />} />
|
<Route index element={<MainBoard />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,22 @@ export const mainApi = createApi({
|
|||||||
]
|
]
|
||||||
: [{ type: 'Task', id: 'LIST' }],
|
: [{ type: 'Task', id: 'LIST' }],
|
||||||
}),
|
}),
|
||||||
|
getTasksForGroup: builder.query<any[], string>({
|
||||||
|
query: (id: string) => ({
|
||||||
|
url: `tasks/project/${id}`,
|
||||||
|
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>({
|
getTask: builder.query<string, string>({
|
||||||
query: (id) => ({
|
query: (id) => ({
|
||||||
url: `tasks/${id}`,
|
url: `tasks/${id}`,
|
||||||
@ -64,7 +80,7 @@ export const mainApi = createApi({
|
|||||||
// PROJECTS
|
// PROJECTS
|
||||||
createProject: builder.mutation({
|
createProject: builder.mutation({
|
||||||
query: (newProject) => ({
|
query: (newProject) => ({
|
||||||
url: 'tasks/create',
|
url: 'projects/create',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: newProject,
|
body: newProject,
|
||||||
}),
|
}),
|
||||||
@ -141,5 +157,5 @@ export const authApi = createApi({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const { useLoginMutation, useRegisterMutation } = authApi;
|
export const { useLoginMutation, useRegisterMutation } = authApi;
|
||||||
export const { useGetTasksQuery, useCreateTaskMutation, useUpdateTaskMutation, useDeleteTaskMutation } = mainApi;
|
export const { useGetTasksQuery, useCreateTaskMutation, useUpdateTaskMutation, useDeleteTaskMutation, useGetTasksForGroupQuery } = mainApi;
|
||||||
export const { useGetProjectsQuery, useCreateProjectMutation, useUpdateProjectMutation, useDeleteProjectMutation } = mainApi;
|
export const { useGetProjectsQuery, useCreateProjectMutation, useUpdateProjectMutation, useDeleteProjectMutation } = mainApi;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user