Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"axios": "^1.7.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
"es-toolkit": "^1.32.0",
"lenis": "^1.1.20",
"lottie-react": "^2.4.1",
Expand Down
36 changes: 36 additions & 0 deletions src/apis/cards/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import axiosClientHelper from '@/utils/network/axiosClientHelper';
import { Card, CardForm, cardSchema, CardsResponse, cardsResponseSchema, GetCardsParams } from './types';

const RESPONSE_INVALID_MESSAGE = '서버에서 받은 데이터가 예상과 다릅니다';

export const postCard = async (cardForm: CardForm) => {
const response = await axiosClientHelper.post<Card>('/cards', cardForm);
const result = cardSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const getCards = async (params: GetCardsParams) => {
const response = await axiosClientHelper.get<CardsResponse>('/cards', { params });
const result = cardsResponseSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const putCard = async (cardId: number, cardForm: CardForm) => {
const response = await axiosClientHelper.put<Card>(`/cards/${cardId}`, cardForm);
const result = cardSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const getCard = async (cardId: number) => {
const response = await axiosClientHelper.get<Card>(`/cards/${cardId}`);
const result = cardSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const deleteCard = async (cardId: number) => {
await axiosClientHelper.delete<void>(`/cards/${cardId}`);
};
57 changes: 57 additions & 0 deletions src/apis/cards/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import isValidDate from '@/utils/isValidDate';
import { z } from 'zod';

const IMAGE_URL = 'https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com/taskify/task_image/';

const cardBaseSchema = z.object({
dashboardId: z.number(),
columnId: z.number(),
title: z.string(),
description: z.string(),
dueDate: z.string().refine((date) => isValidDate(date), {
message: '유효하지 않은 날짜 형식입니다.',
}),
tags: z.array(z.string()),
imageUrl: z
.string()
.url()
.refine((val) => val.startsWith(IMAGE_URL), {
message: '올바르지 않은 이미지 경로입니다.',
}),
});

export const cardSchema = cardBaseSchema.extend({
id: z.number(),
assignee: z.object({
id: z.number(),
nickname: z.string(),
profileImageUrl: z.union([z.string(), z.null(), z.instanceof(URL)]),
}),
teamId: z.string(),
createdAt: z.union([z.string(), z.date()]),
updatedAt: z.union([z.string(), z.date()]),
});

export type Card = z.infer<typeof cardSchema>;

export const cardFormSchema = cardBaseSchema.extend({
assigneeUserId: z.number(),
});

export type CardForm = z.infer<typeof cardFormSchema>;

export const cardsResponseSchema = z.object({
cursorId: z.number().nullable(),
totalCount: z.number(),
cards: z.array(cardSchema),
});

export type CardsResponse = z.infer<typeof cardsResponseSchema>;

export const getCardsParamsSchema = z.object({
size: z.number().optional(),
cursorId: z.number().optional(),
columnId: z.number(),
});

export type GetCardsParams = z.infer<typeof getCardsParamsSchema>;
47 changes: 47 additions & 0 deletions src/apis/columns/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import axiosClientHelper from '@/utils/network/axiosClientHelper';
import { CardImageForm, CardImageResponse, cardImageResponseSchema, Column, ColumnForm, columnSchema, ColumnsResponse, columnsResponseSchema, GetColumnsParams } from './types';

const RESPONSE_INVALID_MESSAGE = '서버에서 받은 데이터가 예상과 다릅니다';

export const postColumn = async (dashboardId: number, columnForm: ColumnForm) => {
const response = await axiosClientHelper.post<Column>('/columns', {
...columnForm,
dashboardId,
});
const result = columnSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const getColumns = async (params: GetColumnsParams) => {
const response = await axiosClientHelper.get<ColumnsResponse>('/columns', {
params,
});

const result = columnsResponseSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const putColumn = async (columnId: number, columnForm: ColumnForm) => {
const response = await axiosClientHelper.put<Column>(`/columns/${columnId}`, columnForm);
const result = columnSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const deleteColumn = async (columnId: number) => {
await axiosClientHelper.delete<void>(`/columns/${columnId}`);
};

export const postCardImage = async (columnId: number, cardImageForm: CardImageForm) => {
const response = await axiosClientHelper.post<CardImageResponse>(`/columns/${columnId}/card-image`, cardImageForm, {
headers: {
'Content-Type': 'multipart/form-data',
},
});

const result = cardImageResponseSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};
43 changes: 43 additions & 0 deletions src/apis/columns/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { z } from 'zod';

export const columnSchema = z.object({
id: z.number(),
title: z.string(),
teamId: z.string(),
dashboardId: z.number(),
createdAt: z.union([z.string(), z.instanceof(URL)]),
updatedAt: z.union([z.string(), z.instanceof(URL)]),
});

export type Column = z.infer<typeof columnSchema>;

export const columnsResponseSchema = z.object({
result: z.string(),
data: z.array(columnSchema),
});

export type ColumnsResponse = z.infer<typeof columnsResponseSchema>;

export const getColumnsParamsSchema = z.object({
dashboardId: z.number(),
});

export type GetColumnsParams = z.infer<typeof getColumnsParamsSchema>;

export const columnFormSchema = z.object({
title: z.string(),
});

export type ColumnForm = z.infer<typeof columnFormSchema>;

export const cardImageFormSchema = z.object({
image: z.instanceof(File),
});

export type CardImageForm = z.infer<typeof cardImageFormSchema>;

export const cardImageResponseSchema = z.object({
imageUrl: z.union([z.string(), z.instanceof(URL)]),
});

export type CardImageResponse = z.infer<typeof cardImageResponseSchema>;
29 changes: 29 additions & 0 deletions src/apis/comments/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import axiosClientHelper from '@/utils/network/axiosClientHelper';
import { Comment, CommentForm, commentSchema, CommentsResponse, commentsResponseSchema, GetCommentsParams, PutCommentForm } from './types';

const RESPONSE_INVALID_MESSAGE = '서버에서 받은 데이터가 예상과 다릅니다';

export const postComment = async (commentForm: CommentForm) => {
const response = await axiosClientHelper.post<Comment>('/comments', commentForm);
const result = commentSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const getComments = async (params: GetCommentsParams) => {
const response = await axiosClientHelper.get<CommentsResponse>('/comments', { params });
const result = commentsResponseSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const putComment = async (commentId: number, putCommentForm: PutCommentForm) => {
const response = await axiosClientHelper.put<Comment>(`/comments/${commentId}`, putCommentForm);
const result = commentSchema.safeParse(response.data);
if (!result.success) throw new Error(RESPONSE_INVALID_MESSAGE);
return result.data;
};

export const deleteComment = async (commentId: number) => {
await axiosClientHelper.delete<void>(`/comments/${commentId}`);
};
46 changes: 46 additions & 0 deletions src/apis/comments/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { z } from 'zod';

export const commentSchema = z.object({
id: z.number(),
content: z.string(),
createdAt: z.union([z.string(), z.date()]),
updatedAt: z.union([z.string(), z.date()]),
cardId: z.number(),
author: z.object({
id: z.number(),
nickname: z.string(),
profileImageUrl: z.union([z.null(), z.string(), z.instanceof(URL)]),
}),
});

export type Comment = z.infer<typeof commentSchema>;

export const commentFormSchema = z.object({
content: z.string(),
cardId: z.number(),
columnId: z.number(),
dashboardId: z.number(),
});

export type CommentForm = z.infer<typeof commentFormSchema>;

export const getCommentsParamsSchema = z.object({
cursorId: z.number().optional(),
size: z.number().optional(),
cardId: z.number(),
});

export type GetCommentsParams = z.infer<typeof getCommentsParamsSchema>;

export const commentsResponseSchema = z.object({
cursorId: z.number().nullable(),
comments: z.array(commentSchema),
});

export type CommentsResponse = z.infer<typeof commentsResponseSchema>;

export const putCommentFormSchema = z.object({
content: z.string(),
});

export type PutCommentForm = z.infer<typeof putCommentFormSchema>;
13 changes: 13 additions & 0 deletions src/utils/isValidDate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import dayjs from 'dayjs';

const isValidDate = (date: string | Date) => {
const format = 'YYYY-MM-DD HH:mm';
if (date instanceof Date) {
const formattedDate = dayjs(date).format(format);
return dayjs(formattedDate, format, true).isValid();
}

return dayjs(date, format, true).isValid();
};

export default isValidDate;