Skip to content

Commit

Permalink
feat: Add project page gf-86 (#109)
Browse files Browse the repository at this point in the history
* feat: add get by id route gf-86

* feat: add page for project gf-86

* fix: fix small errors gf-86

* feat: add styles gf-86

* fix: change project component gf-86

* fix: make description text type gf-86

* fix: fix migrations gf-86

* feat: add clickable project item gf-86

* fix: change const FieldLimit in migration gf-86

* fix: change response type to GetAllItem gf-86

* fix: get all item file name gf-86

* fix: change conditions in project component gf-86

* fix: little layout fix gf-86

* fix: use configureString gf-86

* fix: use loader correct gf-86

* fix: rename findRequest to getId gf-86

* fix: add projectStatus gf-86

* fix: change title gf-86

* fix: remove react-router-dom import gf-86

* feat: add changed package-lock.json gf-86

* fix: remove ProjectGetByIdRequestDto gf-86
  • Loading branch information
HoroshkoMykhailo authored Sep 2, 2024
1 parent c6496a4 commit ba11d74
Show file tree
Hide file tree
Showing 22 changed files with 245 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { type Knex } from "knex";

const TABLE_NAME = "projects";

const ColumnName = {
DESCRIPTION: "description",
} as const;

const FieldLimit = {
DESCRIPTION: 1000,
DESCRIPTION_PREVIOUS: 50,
} as const;

function up(knex: Knex): Promise<void> {
return knex.schema.alterTable(TABLE_NAME, (table) => {
table.string(ColumnName.DESCRIPTION, FieldLimit.DESCRIPTION).alter();
});
}

function down(knex: Knex): Promise<void> {
return knex.schema.alterTable(TABLE_NAME, (table) => {
table
.string(ColumnName.DESCRIPTION, FieldLimit.DESCRIPTION_PREVIOUS)
.alter();
});
}

export { down, up };
1 change: 0 additions & 1 deletion apps/backend/src/modules/projects/libs/types/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export {
type ProjectCreateRequestDto,
type ProjectCreateResponseDto,
type ProjectGetAllItemResponseDto,
type ProjectGetAllResponseDto,
} from "@git-fit/shared";
46 changes: 46 additions & 0 deletions apps/backend/src/modules/projects/project.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ class ProjectController extends BaseController {
method: "GET",
path: ProjectsApiPath.ROOT,
});

this.addRoute({
handler: (options) =>
this.getById(
options as APIHandlerOptions<{
params: { id: string };
}>,
),
method: "GET",
path: ProjectsApiPath.$ID,
});
}

/**
Expand Down Expand Up @@ -129,6 +140,41 @@ class ProjectController extends BaseController {
status: HTTPCode.OK,
};
}

/**
* @swagger
* /projects/{id}:
* get:
* description: Get project by ID
* parameters:
* - in: path
* name: id
* schema:
* type: integer
* required: true
* description: Numeric ID of the project to retrieve
* responses:
* 200:
* description: Successful operation
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: object
* $ref: "#/components/schemas/Project"
*/
private async getById(
options: APIHandlerOptions<{
params: { id: string };
}>,
): Promise<APIHandlerResponse> {
return {
payload: await this.projectService.find(Number(options.params.id)),
status: HTTPCode.OK,
};
}
}

export { ProjectController };
4 changes: 2 additions & 2 deletions apps/backend/src/modules/projects/project.entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Entity } from "~/libs/types/types.js";

import { type ProjectCreateResponseDto } from "./libs/types/types.js";
import { type ProjectGetAllItemResponseDto } from "./libs/types/types.js";

class ProjectEntity implements Entity {
private description!: string;
Expand Down Expand Up @@ -63,7 +63,7 @@ class ProjectEntity implements Entity {
};
}

public toObject(): ProjectCreateResponseDto {
public toObject(): ProjectGetAllItemResponseDto {
return {
description: this.description,
id: this.id as number,
Expand Down
6 changes: 3 additions & 3 deletions apps/backend/src/modules/projects/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { type Service } from "~/libs/types/types.js";
import { ProjectError } from "./libs/exceptions/exceptions.js";
import {
type ProjectCreateRequestDto,
type ProjectCreateResponseDto,
type ProjectGetAllItemResponseDto,
type ProjectGetAllResponseDto,
} from "./libs/types/types.js";
import { ProjectEntity } from "./project.entity.js";
Expand All @@ -20,7 +20,7 @@ class ProjectService implements Service {

public async create(
payload: ProjectCreateRequestDto,
): Promise<ProjectCreateResponseDto> {
): Promise<ProjectGetAllItemResponseDto> {
const { description, name } = payload;
const existingProject = await this.projectRepository.findByName(name);

Expand All @@ -45,7 +45,7 @@ class ProjectService implements Service {
return Promise.resolve(true);
}

public async find(id: number): Promise<ProjectCreateResponseDto> {
public async find(id: number): Promise<ProjectGetAllItemResponseDto> {
const item = await this.projectRepository.find(id);

if (!item) {
Expand Down
5 changes: 5 additions & 0 deletions apps/frontend/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { store } from "~/libs/modules/store/store.js";
import { AccessManagement } from "~/pages/access-management/access-management.jsx";
import { Auth } from "~/pages/auth/auth.jsx";
import { NotFound } from "~/pages/not-found/not-found.jsx";
import { Project } from "~/pages/project/project.jsx";
import { Projects } from "~/pages/projects/projects.jsx";
import { Ui } from "~/pages/ui/ui.jsx";

Expand Down Expand Up @@ -50,6 +51,10 @@ createRoot(document.querySelector("#root") as HTMLElement).render(
element: <Auth />,
path: AppRoute.SIGN_UP,
},
{
element: <Project />,
path: AppRoute.PROJECT,
},
],
element: <App />,
path: AppRoute.ROOT,
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/src/libs/enums/app-route.enum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const AppRoute = {
ACCESS_MANAGEMENT: "/access-management",
ANY: "*",
PROJECT: "/projects/:id",
ROOT: "/",
SIGN_IN: "/sign-in",
SIGN_UP: "/sign-up",
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/libs/hooks/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export {
useController as useFormController,
useWatch as useFormWatch,
} from "react-hook-form";
export { useLocation, useSearchParams } from "react-router-dom";
export { useLocation, useParams, useSearchParams } from "react-router-dom";
21 changes: 20 additions & 1 deletion apps/frontend/src/modules/projects/projects-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { type HTTP } from "~/libs/modules/http/http.js";
import { type Storage } from "~/libs/modules/storage/storage.js";

import { ProjectsApiPath } from "./libs/enums/enums.js";
import { type ProjectGetAllResponseDto } from "./libs/types/types.js";
import {
type ProjectGetAllItemResponseDto,
type ProjectGetAllResponseDto,
} from "./libs/types/types.js";

type Constructor = {
baseUrl: string;
Expand All @@ -16,6 +19,7 @@ class ProjectApi extends BaseHTTPApi {
public constructor({ baseUrl, http, storage }: Constructor) {
super({ baseUrl, http, path: APIPath.PROJECTS, storage });
}

public async getAll(): Promise<ProjectGetAllResponseDto> {
const response = await this.load(
this.getFullEndpoint(ProjectsApiPath.ROOT, {}),
Expand All @@ -28,6 +32,21 @@ class ProjectApi extends BaseHTTPApi {

return await response.json<ProjectGetAllResponseDto>();
}

public async getById(payload: {
id: string;
}): Promise<ProjectGetAllItemResponseDto> {
const response = await this.load(
this.getFullEndpoint(ProjectsApiPath.$ID, { id: payload.id }),
{
contentType: ContentType.JSON,
hasAuth: true,
method: "GET",
},
);

return await response.json<ProjectGetAllItemResponseDto>();
}
}

export { ProjectApi };
17 changes: 15 additions & 2 deletions apps/frontend/src/modules/projects/slices/actions.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import { createAsyncThunk } from "@reduxjs/toolkit";

import { type AsyncThunkConfig } from "~/libs/types/types.js";
import { type ProjectGetAllResponseDto } from "~/modules/projects/projects.js";
import {
type ProjectGetAllItemResponseDto,
type ProjectGetAllResponseDto,
} from "~/modules/projects/projects.js";

import { name as sliceName } from "./project.slice.js";

const getById = createAsyncThunk<
ProjectGetAllItemResponseDto,
{ id: string },
AsyncThunkConfig
>(`${sliceName}/getById`, async (payload, { extra }) => {
const { projectApi } = extra;

return await projectApi.getById(payload);
});

const loadAll = createAsyncThunk<
ProjectGetAllResponseDto,
undefined,
Expand All @@ -15,4 +28,4 @@ const loadAll = createAsyncThunk<
return await projectApi.getAll();
});

export { loadAll };
export { getById, loadAll };
17 changes: 16 additions & 1 deletion apps/frontend/src/modules/projects/slices/project.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,35 @@ import { DataStatus } from "~/libs/enums/enums.js";
import { type ValueOf } from "~/libs/types/types.js";
import { type ProjectGetAllItemResponseDto } from "~/modules/projects/projects.js";

import { loadAll } from "./actions.js";
import { getById, loadAll } from "./actions.js";

type State = {
dataStatus: ValueOf<typeof DataStatus>;
project: null | ProjectGetAllItemResponseDto;
projects: ProjectGetAllItemResponseDto[];
projectStatus: ValueOf<typeof DataStatus>;
};

const initialState: State = {
dataStatus: DataStatus.IDLE,
project: null,
projects: [],
projectStatus: DataStatus.IDLE,
};

const { actions, name, reducer } = createSlice({
extraReducers(builder) {
builder.addCase(getById.pending, (state) => {
state.projectStatus = DataStatus.PENDING;
});
builder.addCase(getById.fulfilled, (state, action) => {
state.project = action.payload;
state.projectStatus = DataStatus.FULFILLED;
});
builder.addCase(getById.rejected, (state) => {
state.project = null;
state.projectStatus = DataStatus.REJECTED;
});
builder.addCase(loadAll.pending, (state) => {
state.dataStatus = DataStatus.PENDING;
});
Expand Down
3 changes: 2 additions & 1 deletion apps/frontend/src/modules/projects/slices/projects.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { loadAll } from "./actions.js";
import { getById, loadAll } from "./actions.js";
import { actions } from "./project.slice.js";

const allActions = {
...actions,
getById,
loadAll,
};

Expand Down
53 changes: 53 additions & 0 deletions apps/frontend/src/pages/project/project.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { PageLayout } from "~/libs/components/components.js";
import { DataStatus } from "~/libs/enums/enums.js";
import {
useAppDispatch,
useAppSelector,
useEffect,
useParams,
} from "~/libs/hooks/hooks.js";
import { actions as projectActions } from "~/modules/projects/projects.js";
import { NotFound } from "~/pages/not-found/not-found.jsx";

import styles from "./styles.module.css";

const Project = (): JSX.Element => {
const dispatch = useAppDispatch();
const { id: projectId } = useParams<{ id: string }>();

const { project, projectStatus } = useAppSelector(({ projects }) => ({
project: projects.project,
projectStatus: projects.projectStatus,
}));

useEffect(() => {
if (projectId) {
void dispatch(projectActions.getById({ id: projectId }));
}
}, [dispatch, projectId]);

const isLoading =
projectStatus === DataStatus.PENDING || projectStatus === DataStatus.IDLE;

const isRejected = projectStatus === DataStatus.REJECTED;

if (isRejected) {
return <NotFound />;
}

return (
<PageLayout isLoading={isLoading}>
<div className={styles["project-layout"]}>
<h1 className={styles["title"]}>{project?.name}</h1>
<div className={styles["project-description-layout"]}>
<h3 className={styles["project-description-title"]}>Description</h3>
<p className={styles["project-description"]}>
{project?.description}
</p>
</div>
</div>
</PageLayout>
);
};

export { Project };
34 changes: 34 additions & 0 deletions apps/frontend/src/pages/project/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.project-layout {
display: flex;
flex-direction: column;
gap: 24px;
align-items: flex-start;
padding: 0;
color: var(--color-text-primary);
}

.title {
margin: 0;
font-size: 30px;
line-height: 120%;
}

.project-description-layout {
display: flex;
flex-direction: column;
gap: 16px;
align-items: flex-start;
padding: 0;
}

.project-description-title {
margin: 0;
font-size: 20px;
line-height: 130%;
}

.project-description {
margin: 0;
font-size: 16px;
line-height: 150%;
}
Loading

0 comments on commit ba11d74

Please sign in to comment.