Skip to content

Commit e4b1a6a

Browse files
committed
Merge branch 'dev' of https://github.com/wafflestudio/23-5-team10-web into 65-feature-login_password
2 parents d69df8b + 862b6e6 commit e4b1a6a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1128
-153
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@
5959
"@tanstack/router-plugin": "^1.145.4",
6060
"@testing-library/dom": "^10.4.1",
6161
"@testing-library/jest-dom": "^6.9.1",
62-
"@testing-library/react": "^16.3.1",
6362
"@testing-library/user-event": "^14.6.1",
63+
"@testing-library/react": "^16.3.1",
6464
"@types/node": "^24.10.1",
6565
"@types/react": "^19.2.5",
6666
"@types/react-dom": "^19.2.3",

src/constants/api-error.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
export const API_ERROR_CODE = {
2+
INTERNAL_SERVER_ERROR: 'INTERNAL_SERVER_ERROR',
3+
INVALID_INPUT_VALUE: 'INVALID_INPUT_VALUE',
4+
ACCESS_DENIED: 'ACCESS_DENIED',
5+
USER_NOT_FOUND: 'USER_NOT_FOUND',
6+
INVALID_PASSWORD: 'INVALID_PASSWORD',
7+
EMAIL_ALREADY_EXISTS: 'EMAIL_ALREADY_EXISTS',
8+
NICKNAME_ALREADY_EXISTS: 'NICKNAME_ALREADY_EXISTS',
9+
INVALID_REFRESH_TOKEN: 'INVALID_REFRESH_TOKEN',
10+
REFRESH_TOKEN_EXPIRED: 'REFRESH_TOKEN_EXPIRED',
11+
REFRESH_TOKEN_REUSE_DETECTED: 'REFRESH_TOKEN_REUSE_DETECTED',
12+
POST_NOT_FOUND: 'POST_NOT_FOUND',
13+
EMPTY_CONTENT: 'EMPTY_CONTENT',
14+
COMMENT_NOT_FOUND: 'COMMENT_NOT_FOUND',
15+
SELF_FOLLOW_NOT_ALLOWED: 'SELF_FOLLOW_NOT_ALLOWED',
16+
} as const
17+
18+
export type ApiErrorCode = (typeof API_ERROR_CODE)[keyof typeof API_ERROR_CODE]
19+
20+
type ApiErrorEntry = {
21+
status: number
22+
message: string
23+
}
24+
25+
export const API_ERROR_INFO: Record<ApiErrorCode, ApiErrorEntry> = {
26+
[API_ERROR_CODE.INTERNAL_SERVER_ERROR]: {
27+
status: 500,
28+
message: '서버 내부 에러가 발생했습니다.',
29+
},
30+
[API_ERROR_CODE.INVALID_INPUT_VALUE]: {
31+
status: 400,
32+
message: '입력값이 올바르지 않습니다.',
33+
},
34+
[API_ERROR_CODE.ACCESS_DENIED]: {
35+
status: 403,
36+
message: '접근 권한이 없습니다.',
37+
},
38+
[API_ERROR_CODE.USER_NOT_FOUND]: {
39+
status: 404,
40+
message: '존재하지 않는 회원입니다.',
41+
},
42+
[API_ERROR_CODE.INVALID_PASSWORD]: {
43+
status: 401,
44+
message: '비밀번호가 일치하지 않습니다.',
45+
},
46+
[API_ERROR_CODE.EMAIL_ALREADY_EXISTS]: {
47+
status: 409,
48+
message: '이미 가입된 이메일입니다.',
49+
},
50+
[API_ERROR_CODE.NICKNAME_ALREADY_EXISTS]: {
51+
status: 409,
52+
message: '이미 가입된 닉네임입니다.',
53+
},
54+
[API_ERROR_CODE.INVALID_REFRESH_TOKEN]: {
55+
status: 401,
56+
message: '인증 정보가 유효하지 않습니다.',
57+
},
58+
[API_ERROR_CODE.REFRESH_TOKEN_EXPIRED]: {
59+
status: 401,
60+
message: '인증 정보가 만료되었습니다.',
61+
},
62+
[API_ERROR_CODE.REFRESH_TOKEN_REUSE_DETECTED]: {
63+
status: 401,
64+
message: '재발급 토큰이 재사용되었습니다.',
65+
},
66+
[API_ERROR_CODE.POST_NOT_FOUND]: {
67+
status: 404,
68+
message: '게시글을 찾을 수 없습니다.',
69+
},
70+
[API_ERROR_CODE.EMPTY_CONTENT]: {
71+
status: 400,
72+
message: '내용이 비어 있습니다.',
73+
},
74+
[API_ERROR_CODE.COMMENT_NOT_FOUND]: {
75+
status: 404,
76+
message: '댓글을 찾을 수 없습니다.',
77+
},
78+
[API_ERROR_CODE.SELF_FOLLOW_NOT_ALLOWED]: {
79+
status: 400,
80+
message: '자기 자신은 팔로우할 수 없습니다.',
81+
},
82+
}

src/entities/post/api/getBookmarkedPosts.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { instance } from '@/shared/api/ky'
22
import {
3-
BookmarkedPostSchema,
3+
PostListItemSchema,
44
ApiResponseSchema,
55
} from '@/entities/post/model/schema'
6-
import type { BookmarkedPost } from '@/entities/post/model/types'
6+
import type { PostListItem } from '@/entities/post/model/types'
77

8-
export async function getBookmarkedPosts(): Promise<BookmarkedPost[]> {
8+
export async function getBookmarkedPosts(): Promise<PostListItem[]> {
99
const response = await instance.get('api/v1/posts/bookmarks')
1010

1111
const raw = await response.json()
1212

13-
const parsed = ApiResponseSchema(BookmarkedPostSchema.array()).parse(raw)
13+
const parsed = ApiResponseSchema(PostListItemSchema.array()).parse(raw)
1414

1515
if (!parsed.success) {
1616
throw new Error(parsed.message || 'Failed to load bookmarked posts')
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { instance } from '@/shared/api/ky'
2+
import {
3+
PostListItemSchema,
4+
ApiResponseSchema,
5+
} from '@/entities/post/model/schema'
6+
import type { PostListItem } from '@/entities/post/model/types'
7+
8+
export async function getExplorePosts(): Promise<PostListItem[]> {
9+
const response = await instance.get('api/v1/posts/search')
10+
11+
const raw = await response.json()
12+
13+
const parsed = ApiResponseSchema(PostListItemSchema.array()).parse(raw)
14+
15+
if (!parsed.success) {
16+
throw new Error(parsed.message || 'Failed to load explore posts')
17+
}
18+
19+
return parsed.data
20+
}

src/entities/post/model/hooks/useBookmarkedPostsQuery.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { useQuery } from '@tanstack/react-query'
22
import { getBookmarkedPosts } from '@/entities/post/api/getBookmarkedPosts'
3-
import type { BookmarkedPost } from '@/entities/post/model/types'
3+
import type { PostListItem } from '@/entities/post/model/types'
44

55
export function useBookmarkedPostsQuery() {
6-
return useQuery<BookmarkedPost[]>({
6+
return useQuery<PostListItem[]>({
77
queryKey: ['posts', 'bookmarks'],
88
queryFn: () => getBookmarkedPosts(),
99
retry: false,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { useQuery } from '@tanstack/react-query'
2+
import { getExplorePosts } from '@/entities/post/api/getExplorePosts'
3+
import type { PostListItem } from '@/entities/post/model/types'
4+
5+
export function useExplorePostsQuery() {
6+
return useQuery<PostListItem[]>({
7+
queryKey: ['posts', 'explore'],
8+
queryFn: () => getExplorePosts(),
9+
retry: false,
10+
})
11+
}

src/entities/post/model/schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const PostImageSchema = z.object({
66
orderIndex: z.number(),
77
})
88

9-
export const BookmarkedPostSchema = z.object({
9+
export const PostListItemSchema = z.object({
1010
id: z.number(),
1111
userId: z.number(),
1212
nickname: z.string(),

src/entities/post/model/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { z } from 'zod'
22
import {
3-
BookmarkedPostSchema,
3+
PostListItemSchema,
44
PostImageSchema,
55
ApiResponseSchema,
66
} from './schema'
77

88
export type PostImage = z.infer<typeof PostImageSchema>
9-
export type BookmarkedPost = z.infer<typeof BookmarkedPostSchema>
9+
export type PostListItem = z.infer<typeof PostListItemSchema>
1010

1111
export type ApiResponse<T> = z.infer<
1212
ReturnType<typeof ApiResponseSchema<z.ZodType<T>>>

src/features/create-post/ui/CreateModal.tsx

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@ function CreateModalInner({
7171
<DialogContent
7272
showCloseButton={false}
7373
className={[
74-
'flex flex-col gap-0 overflow-hidden rounded-4xl bg-white p-0 transition-[max-width] duration-300',
74+
'flex flex-col gap-0 overflow-hidden rounded-4xl bg-white p-0 transition-[width,max-width] duration-300 sm:h-auto sm:max-w-[calc(100vw-2rem)]',
7575
isDetails
76-
? 'sm:h-[min(80vh,560px)] sm:max-w-4xl'
77-
: 'aspect-square sm:h-[min(80vh,560px)] sm:max-w-xl',
76+
? 'sm:w-[calc(80vh-51px+340px)] sm:max-w-[849px]'
77+
: 'sm:w-[calc(80vh-51px)] sm:max-w-[509px]',
7878
].join(' ')}
7979
>
8080
<CreateModalHeader
@@ -89,7 +89,7 @@ function CreateModalInner({
8989
{isUploaded ? (
9090
isDetails ? (
9191
<div className="flex min-h-0 flex-1 flex-col sm:flex-row">
92-
<div className="flex min-h-0 flex-1 sm:w-[560px] sm:shrink-0">
92+
<div className="flex aspect-square w-full sm:h-[calc(80vh-51px)] sm:max-h-[509px] sm:w-[calc(80vh-51px)] sm:max-w-[509px] sm:flex-none">
9393
<PreviewPane
9494
activePreviewUrl={activePreviewUrl}
9595
filesCount={files.length}
@@ -107,26 +107,30 @@ function CreateModalInner({
107107
</div>
108108
</div>
109109
) : (
110-
<PreviewPane
111-
activePreviewUrl={activePreviewUrl}
112-
filesCount={files.length}
113-
activeIndex={carousel.activeIndex}
114-
canGoPrev={carousel.canGoPrev}
115-
canGoNext={carousel.canGoNext}
116-
dots={carousel.dots}
117-
goPrev={carousel.goPrev}
118-
goNext={carousel.goNext}
119-
/>
110+
<div className="flex aspect-square w-full sm:h-[calc(80vh-51px)] sm:max-h-[509px] sm:w-[calc(80vh-51px)] sm:max-w-[509px]">
111+
<PreviewPane
112+
activePreviewUrl={activePreviewUrl}
113+
filesCount={files.length}
114+
activeIndex={carousel.activeIndex}
115+
canGoPrev={carousel.canGoPrev}
116+
canGoNext={carousel.canGoNext}
117+
dots={carousel.dots}
118+
goPrev={carousel.goPrev}
119+
goNext={carousel.goNext}
120+
/>
121+
</div>
120122
)
121123
) : (
122-
<Dropzone
123-
accept={CREATE_POST_IMAGE_ACCEPT}
124-
multiple
125-
maxSizeBytes={MAX_IMAGE_FILE_SIZE_BYTES}
126-
onDropFiles={handleDropFiles}
127-
>
128-
{(api) => <EmptyDropzoneState {...api} />}
129-
</Dropzone>
124+
<div className="flex aspect-square w-full sm:h-[calc(80vh-51px)] sm:max-h-[509px] sm:w-[calc(80vh-51px)] sm:max-w-[509px]">
125+
<Dropzone
126+
accept={CREATE_POST_IMAGE_ACCEPT}
127+
multiple
128+
maxSizeBytes={MAX_IMAGE_FILE_SIZE_BYTES}
129+
onDropFiles={handleDropFiles}
130+
>
131+
{(api) => <EmptyDropzoneState {...api} />}
132+
</Dropzone>
133+
</div>
130134
)}
131135
</DialogContent>
132136
</Dialog>

src/features/create-post/ui/EmptyDropzoneState.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export function EmptyDropzoneState({
3131
>
3232
<ImagePlus className="size-10" />
3333

34-
<p className="text-lg font-medium">
34+
<p className="text-lg font-medium break-keep">
3535
사진과 동영상을 여기에 끌어다 놓으세요
3636
</p>
3737

0 commit comments

Comments
 (0)