diff --git a/backend/src/controllers.ts b/backend/src/controllers.ts
index 427905f..416f6f0 100644
--- a/backend/src/controllers.ts
+++ b/backend/src/controllers.ts
@@ -1,9 +1,9 @@
import { Request, Response } from 'express';
+import jwt from 'jsonwebtoken';
import { getRepository } from 'typeorm';
import { Project } from './entities/Project';
-import { ProjectMember } from './entities/ProjectMember'
+import { ProjectMember } from './entities/ProjectMember';
import { Task } from './entities/Task';
-import jwt from 'jsonwebtoken';
import { User } from './entities/User';
const SECRET_KEY = process.env.JWT_SECRET || 'your_secret_key';
diff --git a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx
index fa0ed1d..7185d91 100644
--- a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx
+++ b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx
@@ -6,12 +6,15 @@ import {
useGetTasksForGroupQuery,
} from '../../services/mainApi';
import TaskCard from '../TaskCard/TaskCard';
-import CreateTaskDialog from './CreateTaskDialog/CreateTaskDialog';
-import DeleteProjectDialog from './DeleteProjectDialog/CreateTaskDialog';
+import AddUserToProjectDialog from '../dialogs/AddUserToProjectDialog/AddUserToProjectDialog';
+import CreateTaskDialog from '../dialogs/CreateTaskDialog/CreateTaskDialog';
+import DeleteProjectDialog from '../dialogs/DeleteProjectDialog/CreateTaskDialog';
+
+import { PlusIcon } from '@radix-ui/react-icons';
type TCardGroup = {
id: string;
- title: string;
+ title: string;
};
export default function CardGroup(props: TCardGroup) {
@@ -56,14 +59,19 @@ export default function CardGroup(props: TCardGroup) {
-
+
-
+
+
+
+
+
)}
diff --git a/cool_todo_manager/src/components/MainBoard/MainBoard.tsx b/cool_todo_manager/src/components/MainBoard/MainBoard.tsx
index 94c6331..41854df 100644
--- a/cool_todo_manager/src/components/MainBoard/MainBoard.tsx
+++ b/cool_todo_manager/src/components/MainBoard/MainBoard.tsx
@@ -2,41 +2,24 @@ import { DragDropContext } from '@hello-pangea/dnd';
import { Button, Flex, ScrollArea } from '@radix-ui/themes';
import {
useCreateProjectMutation,
- useCreateTaskMutation,
- useGetProjectsQuery,
+ useGetProjectsQuery
} from '../../services/mainApi';
import CardGroup from '../CardGroup/CardGroup';
+import CreateProjectDialog from '../dialogs/CreateProjectDialog/CreateProjectDialog';
export default function MainBoard() {
const dragEndHandle = (result: TDragResult) => {
result;
};
- const [create] = useCreateTaskMutation();
const [createProject] = useCreateProjectMutation();
const { data: cringe, isLoading } = useGetProjectsQuery({});
- const onClick = () => {
- create({
- title: 'My Task 123',
- projectId: 4,
- assignedUserId: 1,
- deadline: '2025-03-01T12:00:00Z',
- });
- };
-
- const onClick1 = () => {
- createProject({
- title: 'My project test 12',
- description: 'Test desc 123 123 123',
- });
- };
-
return (
<>
-
-
+
+
{!isLoading &&
(cringe as any[]).map((item: any) => (
@@ -44,7 +27,10 @@ export default function MainBoard() {
-
+
+
+
+
>
);
}
diff --git a/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx b/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx
new file mode 100644
index 0000000..65727f8
--- /dev/null
+++ b/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx
@@ -0,0 +1,92 @@
+import { Button, Dialog, Flex, Select } from '@radix-ui/themes';
+import { PropsWithChildren, useState } from 'react';
+import {
+ useAddProjectMemberMutation,
+ useGetAllUsersQuery,
+} from '../../../services/mainApi';
+
+type TAddUserToProjectDialog = {
+ projectId: number;
+} & PropsWithChildren;
+
+export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) {
+ const { projectId, children } = props;
+ const { data: users, isLoading, error, refetch } = useGetAllUsersQuery({});
+ const [addProjectMember, { isLoading: isAdding }] =
+ useAddProjectMemberMutation();
+ const [selectedUserId, setSelectedUserId] = useState(null);
+
+ const handleAddUser = async () => {
+ if (selectedUserId) {
+ try {
+ await addProjectMember({
+ projectId,
+ memberId: selectedUserId,
+ }).unwrap();
+ setSelectedUserId(null);
+ } catch (err) {
+ console.error('Failed to add user:', err);
+ }
+ }
+ };
+
+
+ return (
+ refetch()}>
+ {children}
+
+
+ Add User to Project
+
+
+ {isLoading ? (
+ Loading users...
+ ) : error ? (
+ Error loading users.
+ ) : (
+
+ setSelectedUserId(Number(value))
+ }
+ required
+ >
+
+ {selectedUserId
+ ? users?.find(
+ (user: any) =>
+ user.id === selectedUserId,
+ )?.username
+ : 'Select a user'}
+
+
+ {users?.map((user: any) => (
+
+ {user.username}
+
+ ))}
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/cool_todo_manager/src/components/dialogs/CreateProjectDialog/CreateProjectDialog.tsx b/cool_todo_manager/src/components/dialogs/CreateProjectDialog/CreateProjectDialog.tsx
new file mode 100644
index 0000000..8d74bf8
--- /dev/null
+++ b/cool_todo_manager/src/components/dialogs/CreateProjectDialog/CreateProjectDialog.tsx
@@ -0,0 +1,59 @@
+import { Button, Dialog, Flex, TextArea, TextField } from '@radix-ui/themes';
+import { PropsWithChildren, useState } from 'react';
+
+type TCreateProjectDialog = {
+ onCreate: (f: any) => void;
+} & PropsWithChildren;
+
+export default function CreateProjectDialog(props: TCreateProjectDialog) {
+ const [title, setTitle] = useState('');
+ const [description, setDescription] = useState('');
+
+ const handleCreate = () => {
+ props.onCreate({title, description});
+ setTitle('');
+ setDescription('');
+ };
+
+ return (
+
+
+ {props.children}
+
+
+
+ Create a New Project
+
+
+ setTitle(e.target.value)}
+ required
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/cool_todo_manager/src/components/CardGroup/CreateTaskDialog/CreateTaskDialog.tsx b/cool_todo_manager/src/components/dialogs/CreateTaskDialog/CreateTaskDialog.tsx
similarity index 100%
rename from cool_todo_manager/src/components/CardGroup/CreateTaskDialog/CreateTaskDialog.tsx
rename to cool_todo_manager/src/components/dialogs/CreateTaskDialog/CreateTaskDialog.tsx
diff --git a/cool_todo_manager/src/components/CardGroup/DeleteProjectDialog/CreateTaskDialog.tsx b/cool_todo_manager/src/components/dialogs/DeleteProjectDialog/CreateTaskDialog.tsx
similarity index 100%
rename from cool_todo_manager/src/components/CardGroup/DeleteProjectDialog/CreateTaskDialog.tsx
rename to cool_todo_manager/src/components/dialogs/DeleteProjectDialog/CreateTaskDialog.tsx
diff --git a/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx b/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx
index 21168d0..a23a0fb 100644
--- a/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx
+++ b/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx
@@ -8,8 +8,33 @@ import {
TextField,
} from '@radix-ui/themes';
import { Form } from 'radix-ui';
+import { useState } from 'react';
+import { useRegisterMutation } from '../../../services/mainApi';
export default function RegisterPage() {
+ const [register, { isLoading, error }] = useRegisterMutation();
+ const [formData, setFormData] = useState({
+ username: '',
+ email: '',
+ password: '',
+ });
+
+ const handleChange = (e: React.ChangeEvent) => {
+ setFormData({
+ ...formData,
+ [e.target.name]: e.target.value,
+ });
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ try {
+ await register(formData).unwrap();
+ } catch (err) {
+ console.error('Failed to register:', err);
+ }
+ };
+
return (
@@ -17,8 +42,27 @@ export default function RegisterPage() {
e.preventDefault()}
+ onSubmit={handleSubmit}
>
+
+
+ Username is required
+
+
+
+
+
+
+
+
+
Email is required
@@ -31,8 +75,11 @@ export default function RegisterPage() {
@@ -49,16 +96,24 @@ export default function RegisterPage() {
type="password"
placeholder="Password"
required
+ value={formData.password}
+ onChange={handleChange}
>
-
);
-}
+}
\ No newline at end of file
diff --git a/cool_todo_manager/src/services/mainApi.ts b/cool_todo_manager/src/services/mainApi.ts
index 3dbcdb8..ddf181c 100644
--- a/cool_todo_manager/src/services/mainApi.ts
+++ b/cool_todo_manager/src/services/mainApi.ts
@@ -3,7 +3,7 @@ import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const mainApi = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
- baseUrl: 'http://109.107.166.17:3000/api/',
+ baseUrl: 'http://109.107.166.17:5000/api/',
prepareHeaders: (headers) => {
headers.set(
'Authorization',
@@ -12,7 +12,7 @@ export const mainApi = createApi({
return headers;
},
}),
- tagTypes: ['Task', 'Project'],
+ tagTypes: ['Task', 'Project', 'ProjectMembers'],
endpoints: (builder) => ({
getTasks: builder.query({
query: () => ({
@@ -57,7 +57,7 @@ export const mainApi = createApi({
query: (task) => ({
url: `tasks/${task.id}`,
method: 'PATCH',
- body: {status: task.status},
+ body: { status: task.status },
}),
invalidatesTags: (result, error, id) => [{ type: 'Task', id }],
}),
@@ -88,7 +88,7 @@ export const mainApi = createApi({
}),
getProjects: builder.query({
query: () => ({
- url: 'projects',
+ url: 'projects/my',
method: 'GET',
}),
providesTags: (result, error, id) => [
@@ -115,7 +115,40 @@ export const mainApi = createApi({
url: `projects/${id}`,
method: 'DELETE',
}),
- invalidatesTags: (result, error, id) => [{ type: 'Project', id }],
+ invalidatesTags: (result, error, id) => [
+ { type: 'Project', id: 'LIST' },
+ ],
+ }),
+
+ // PROJECT MEMBERS
+ addProjectMember: builder.mutation({
+ query: ({ projectId, memberId }) => ({
+ url: `projects/${projectId}/members/add`,
+ method: 'POST',
+ body: { userId: memberId, role: '' },
+ }),
+ invalidatesTags: (result, error, args) => [
+ { type: 'ProjectMembers', id: 'LIST' },
+ ],
+ }),
+ getProjectMembers: builder.query({
+ query: (id) => ({
+ url: `projects/${id}/members`,
+ method: 'GET',
+ }),
+ providesTags: (result, error, args) => [
+ { type: 'ProjectMembers', id: 'LIST' },
+ ],
+ }),
+ removeProjectMember: builder.mutation({
+ query: ({ projectId, memberId }) => ({
+ url: `projects/${projectId}/members/remove`,
+ method: 'DELETE',
+ body: { userId: memberId },
+ }),
+ invalidatesTags: (result, error, id) => [
+ { type: 'ProjectMembers', id: 'LIST' },
+ ],
}),
}),
});
@@ -123,39 +156,76 @@ export const mainApi = createApi({
export const authApi = createApi({
reducerPath: 'authApi',
baseQuery: fetchBaseQuery({
- baseUrl: 'http://109.107.166.17:3000/api/auth/',
-
+ baseUrl: 'http://109.107.166.17:4000/api/',
}),
endpoints: (builder) => ({
login: builder.mutation({
query: (credentials) => ({
- url: 'login',
+ url: '/auth/login',
method: 'POST',
body: credentials,
}),
async onQueryStarted(arg, { queryFulfilled }) {
try {
const response = await queryFulfilled;
- if (response.data) {
+ if (response.data.access_token) {
localStorage.setItem(
'token',
response.data.access_token,
);
- window.location.href = '/';
+ if (response.meta?.response?.status === 201)
+ window.location.href = '/';
}
} catch (error) {}
},
}),
- register: builder.mutation({
- query: (credentials) => ({
- url: 'register',
- method: 'POST',
- body: credentials,
- }),
- }),
+ register: builder.mutation({
+ query: (credentials) => ({
+ url: '/auth/register',
+ method: 'POST',
+ body: credentials,
+ }),
+ onQueryStarted: async (arg, { queryFulfilled }) => {
+ try {
+ const response = await queryFulfilled;
+ if (response.meta?.response?.status === 201)
+ window.location.href = '/login';
+ } catch (error) {}
+ },
+ }),
+
+ // USERS
+ getAllUsers: builder.query({
+ query: () => ({
+ url: '/users',
+ method: 'GET',
+ }),
+ keepUnusedDataFor: 0,
+
+ }),
}),
});
-export const { useLoginMutation, useRegisterMutation } = authApi;
-export const { useGetTasksQuery, useCreateTaskMutation, useUpdateTaskMutation, useDeleteTaskMutation, useGetTasksForGroupQuery } = mainApi;
-export const { useGetProjectsQuery, useCreateProjectMutation, useUpdateProjectMutation, useDeleteProjectMutation } = mainApi;
+export const { useLoginMutation, useRegisterMutation, useGetAllUsersQuery } =
+ authApi;
+
+export const {
+ useGetTasksQuery,
+ useCreateTaskMutation,
+ useUpdateTaskMutation,
+ useDeleteTaskMutation,
+ useGetTasksForGroupQuery,
+} = mainApi;
+
+export const {
+ useGetProjectsQuery,
+ useCreateProjectMutation,
+ useUpdateProjectMutation,
+ useDeleteProjectMutation,
+} = mainApi;
+
+export const {
+ useRemoveProjectMemberMutation,
+ useAddProjectMemberMutation,
+ useGetProjectMembersQuery,
+} = mainApi;