still rainbow vommit
This commit is contained in:
parent
f857ae3e2d
commit
8032f415dc
@ -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';
|
||||
|
||||
@ -6,8 +6,11 @@ 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;
|
||||
@ -56,14 +59,19 @@ export default function CardGroup(props: TCardGroup) {
|
||||
</Box>
|
||||
<Flex gap={'2'} className="mx-auto w-full mt-2">
|
||||
<CreateTaskDialog onClose={createTask}>
|
||||
<Button>Add Task</Button>
|
||||
<Button className='!grow-1'>Add Task</Button>
|
||||
</CreateTaskDialog>
|
||||
<DeleteProjectDialog onClose={deleteGroup}>
|
||||
<Button color="red" onClick={deleteGroup}>
|
||||
Delete Project
|
||||
</Button>
|
||||
<Button className='!grow-1' color="red">Delete Project</Button>
|
||||
</DeleteProjectDialog>
|
||||
</Flex>
|
||||
|
||||
<AddUserToProjectDialog projectId={+props.id}>
|
||||
<Button className='!mt-2'>
|
||||
<PlusIcon />
|
||||
Add user
|
||||
</Button>
|
||||
</AddUserToProjectDialog>
|
||||
</Box>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<DragDropContext onDragEnd={dragEndHandle}>
|
||||
<ScrollArea scrollbars='horizontal' className='pb-3'>
|
||||
<Flex gap={'2'} className='min-w-fit'>
|
||||
<ScrollArea scrollbars="horizontal" className="pb-3">
|
||||
<Flex gap={'2'} className="min-w-fit">
|
||||
{!isLoading &&
|
||||
(cringe as any[]).map((item: any) => (
|
||||
<CardGroup id={item.id} title={item.title} />
|
||||
@ -44,7 +27,10 @@ export default function MainBoard() {
|
||||
</Flex>
|
||||
</ScrollArea>
|
||||
</DragDropContext>
|
||||
<Button onClick={onClick1}>Create project</Button>
|
||||
|
||||
<CreateProjectDialog onCreate={createProject}>
|
||||
<Button>Create project</Button>
|
||||
</CreateProjectDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -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<number | null>(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 (
|
||||
<Dialog.Root onOpenChange={() => refetch()}>
|
||||
<Dialog.Trigger>{children}</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content maxWidth="450px">
|
||||
<Dialog.Title>Add User to Project</Dialog.Title>
|
||||
|
||||
<Flex direction="column" gap="3" mt="4">
|
||||
{isLoading ? (
|
||||
<span>Loading users...</span>
|
||||
) : error ? (
|
||||
<span>Error loading users.</span>
|
||||
) : (
|
||||
<Select.Root
|
||||
value={selectedUserId?.toString() || ''}
|
||||
onValueChange={(value) =>
|
||||
setSelectedUserId(Number(value))
|
||||
}
|
||||
required
|
||||
>
|
||||
<Select.Trigger aria-label="User">
|
||||
{selectedUserId
|
||||
? users?.find(
|
||||
(user: any) =>
|
||||
user.id === selectedUserId,
|
||||
)?.username
|
||||
: 'Select a user'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{users?.map((user: any) => (
|
||||
<Select.Item key={user.id} value={user.id}>
|
||||
{user.username}
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<Flex gap="3" mt="4" justify="end">
|
||||
<Dialog.Close>
|
||||
<Button variant="soft" color="gray">
|
||||
Cancel
|
||||
</Button>
|
||||
</Dialog.Close>
|
||||
<Dialog.Close>
|
||||
<Button
|
||||
color="green"
|
||||
onClick={handleAddUser}
|
||||
disabled={!selectedUserId || isAdding}
|
||||
>
|
||||
{isAdding ? 'Adding...' : 'Add'}
|
||||
</Button>
|
||||
</Dialog.Close>
|
||||
</Flex>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
@ -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 (
|
||||
<Dialog.Root>
|
||||
<Dialog.Trigger>
|
||||
{props.children}
|
||||
</Dialog.Trigger>
|
||||
|
||||
<Dialog.Content maxWidth="450px">
|
||||
<Dialog.Title>Create a New Project</Dialog.Title>
|
||||
|
||||
<Flex direction="column" gap="3" mt="4">
|
||||
<TextField.Root
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
required
|
||||
></TextField.Root>
|
||||
<TextArea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Optional description"
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="3" mt="4" justify="end">
|
||||
<Dialog.Close>
|
||||
<Button variant="soft" color="gray">
|
||||
Cancel
|
||||
</Button>
|
||||
</Dialog.Close>
|
||||
<Dialog.Close>
|
||||
<Button
|
||||
color="green"
|
||||
onClick={handleCreate}
|
||||
disabled={!title}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Dialog.Close>
|
||||
</Flex>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
);
|
||||
}
|
||||
@ -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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<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">
|
||||
@ -17,8 +42,27 @@ export default function RegisterPage() {
|
||||
</Heading>
|
||||
<Form.Root
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={(e) => e.preventDefault()}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Form.Field name="username">
|
||||
<Form.Message match="valueMissing">
|
||||
<Text>Username is required</Text>
|
||||
</Form.Message>
|
||||
|
||||
<Form.Control asChild>
|
||||
<TextField.Root
|
||||
type="text"
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
required
|
||||
value={formData.username}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<TextField.Slot />
|
||||
</TextField.Root>
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
|
||||
<Form.Field name="email">
|
||||
<Form.Message match="valueMissing">
|
||||
<Text>Email is required</Text>
|
||||
@ -31,8 +75,11 @@ export default function RegisterPage() {
|
||||
<Form.Control asChild>
|
||||
<TextField.Root
|
||||
type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<TextField.Slot />
|
||||
</TextField.Root>
|
||||
@ -49,16 +96,24 @@ export default function RegisterPage() {
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
value={formData.password}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<TextField.Slot />
|
||||
</TextField.Root>
|
||||
</Form.Control>
|
||||
</Form.Field>
|
||||
|
||||
<Button onClick={(e) => e.preventDefault()} className="mt-4">
|
||||
<Button type="submit" disabled={isLoading} className="mt-4">
|
||||
Register
|
||||
</Button>
|
||||
|
||||
{error && (
|
||||
<Box className="text-red-500">
|
||||
<Text>Error registering user.</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box className="w-full !flex justify-center">
|
||||
<Link href="/login">
|
||||
<Text>Login</Text>
|
||||
|
||||
@ -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<string[], void>({
|
||||
query: () => ({
|
||||
@ -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,24 +156,24 @@ 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,
|
||||
);
|
||||
if (response.meta?.response?.status === 201)
|
||||
window.location.href = '/';
|
||||
}
|
||||
} catch (error) {}
|
||||
@ -148,14 +181,51 @@ export const authApi = createApi({
|
||||
}),
|
||||
register: builder.mutation({
|
||||
query: (credentials) => ({
|
||||
url: 'register',
|
||||
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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user