From 657efc9abc7f08b6122320f5206e662fc5991c2e Mon Sep 17 00:00:00 2001 From: Seungmin Cha <75214259+Virtuso1225@users.noreply.github.com> Date: Fri, 23 May 2025 08:53:00 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20?= =?UTF-8?q?=EB=B2=84=EC=A0=84=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=ED=95=98=EB=8A=94=20=ED=83=AD=EC=9D=84=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=95=B4=EC=9A=94.=20-=201=20(#203)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + packages/api/ku-key/.openapi-generator/FILES | 1 + packages/api/ku-key/api/banner-api.ts | 14 +- packages/api/ku-key/api/friendship-api.ts | 8 +- packages/api/ku-key/api/timetable-api.ts | 6 +- packages/api/ku-key/models/banner-dto.ts | 6 + .../get-nullable-timetable-response-dto.ts | 29 ++++ packages/api/ku-key/models/index.ts | 1 + scripts/re-create-api-json.js | 2 +- src/components/ui/profile/index.tsx | 7 +- .../Post/components/MobileFeedItem/index.tsx | 152 ++++++++++++++++++ .../components/MobileFeedItem/style.css.ts | 58 +++++++ .../Community/CommunitySelectTab/index.tsx | 5 +- .../Community/CommunitySelectTab/style.css.ts | 11 ++ .../Community/HotPostPreview/style.css.ts | 8 +- src/features/Community/PostAll/index.tsx | 17 +- src/features/Community/PostAll/style.css.ts | 7 +- .../Community/PostSearch/style.css.ts | 8 +- src/pages/Community/All/style.css.ts | 2 +- src/ui/Button/index.tsx | 21 ++- src/ui/Button/style.css.ts | 12 +- yarn.lock | 8 + 22 files changed, 358 insertions(+), 26 deletions(-) create mode 100644 packages/api/ku-key/models/get-nullable-timetable-response-dto.ts create mode 100644 src/domain/Post/components/MobileFeedItem/index.tsx create mode 100644 src/domain/Post/components/MobileFeedItem/style.css.ts diff --git a/package.json b/package.json index fa4ca587..42692154 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@vanilla-extract/css": "^1.17.0", "@vanilla-extract/recipes": "^0.5.5", "axios": "^1.6.8", + "clsx": "^2.1.1", "country-flag-icons": "^1.5.13", "date-fns": "^3.6.0", "embla-carousel-autoplay": "^8.2.0", diff --git a/packages/api/ku-key/.openapi-generator/FILES b/packages/api/ku-key/.openapi-generator/FILES index bc87100f..cff524de 100644 --- a/packages/api/ku-key/.openapi-generator/FILES +++ b/packages/api/ku-key/.openapi-generator/FILES @@ -76,6 +76,7 @@ models/get-friend-response-dto.ts models/get-hot-club-response-dto.ts models/get-my-comment-list-response-dto.ts models/get-notice-response-dto.ts +models/get-nullable-timetable-response-dto.ts models/get-point-history-response-dto.ts models/get-post-list-response-dto.ts models/get-post-list-with-board-response-dto.ts diff --git a/packages/api/ku-key/api/banner-api.ts b/packages/api/ku-key/api/banner-api.ts index d3dcf7b0..32714852 100644 --- a/packages/api/ku-key/api/banner-api.ts +++ b/packages/api/ku-key/api/banner-api.ts @@ -95,12 +95,14 @@ const bannerIdDeleteAxiosParamCreator = async ( * @summary 배너 이미지 생성 * @param {any} image 배너 이미지 파일 * @param {string} title 배너 제목 + * @param {string} [link] 링크 * @param {*} [options] Override http request option. * @throws {RequiredError} */ const bannerPostAxiosParamCreator = async ( image: any, title: string, + link?: string, options: AxiosRequestConfig = {}, configuration?: Configuration, ): Promise => { @@ -125,6 +127,10 @@ const bannerPostAxiosParamCreator = async ( localVarFormParams.append('title', title as any) } + if (link !== undefined) { + localVarFormParams.append('link', link as any) + } + localVarHeaderParameter['Content-Type'] = 'multipart/form-data' setSearchParams(localVarUrlObj, localVarQueryParameter) @@ -175,16 +181,18 @@ const bannerIdDeleteFp = async ( * @summary 배너 이미지 생성 * @param {any} image 배너 이미지 파일 * @param {string} title 배너 제목 + * @param {string} [link] 링크 * @param {*} [options] Override http request option. * @throws {RequiredError} */ const bannerPostFp = async ( image: any, title: string, + link?: string, options?: AxiosRequestConfig, configuration?: Configuration, ): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> => { - const localVarAxiosArgs = await bannerPostAxiosParamCreator(image, title, options, configuration) + const localVarAxiosArgs = await bannerPostAxiosParamCreator(image, title, link, options, configuration) return createRequestFunction(localVarAxiosArgs, globalAxios, configuration) } @@ -246,6 +254,7 @@ export const bannerIdDelete = ({ export type BannerPostRequestParams = { image: any title: string + link?: string options?: any } @@ -254,6 +263,7 @@ export type BannerPostRequestParams = { * @summary 배너 이미지 생성 * @param {any} image 배너 이미지 파일 * @param {string} title 배너 제목 + * @param {string} [link] 링크 * @param {*} [options] Override http request option. * @throws {RequiredError} */ @@ -267,7 +277,7 @@ export const bannerPost = ({ axios?: AxiosInstance }) => { return (params: BannerPostRequestParams): AxiosPromise => { - return bannerPostFp(params.image, params.title, params.options, configuration).then(request => + return bannerPostFp(params.image, params.title, params.link, params.options, configuration).then(request => request(axios, basePath), ) } diff --git a/packages/api/ku-key/api/friendship-api.ts b/packages/api/ku-key/api/friendship-api.ts index 886a9757..6212400d 100644 --- a/packages/api/ku-key/api/friendship-api.ts +++ b/packages/api/ku-key/api/friendship-api.ts @@ -25,9 +25,9 @@ import { DeleteFriendshipResponseDto } from '../models' // @ts-ignore import { GetFriendResponseDto } from '../models' // @ts-ignore -import { GetReceivedFriendshipRequestCountDto } from '../models' +import { GetNullableTimetableResponseDto } from '../models' // @ts-ignore -import { GetTimetableByTimetableIdDto } from '../models' +import { GetReceivedFriendshipRequestCountDto } from '../models' // @ts-ignore import { GetWaitingFriendResponseDto } from '../models' // @ts-ignore @@ -455,7 +455,7 @@ const friendshipFriendTimetableGetFp = async ( semester: string, options?: AxiosRequestConfig, configuration?: Configuration, -): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> => { +): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> => { const localVarAxiosArgs = await friendshipFriendTimetableGetAxiosParamCreator( username, year, @@ -652,7 +652,7 @@ export const friendshipFriendTimetableGet = ({ basePath?: string axios?: AxiosInstance }) => { - return (params: FriendshipFriendTimetableGetRequestParams): AxiosPromise> => { + return (params: FriendshipFriendTimetableGetRequestParams): AxiosPromise => { return friendshipFriendTimetableGetFp( params.username, params.year, diff --git a/packages/api/ku-key/api/timetable-api.ts b/packages/api/ku-key/api/timetable-api.ts index e86827f2..b7c6855b 100644 --- a/packages/api/ku-key/api/timetable-api.ts +++ b/packages/api/ku-key/api/timetable-api.ts @@ -29,7 +29,7 @@ import { CreateTimetableCourseResponseDto } from '../models' // @ts-ignore import { CreateTimetableDto } from '../models' // @ts-ignore -import { GetTimetableByTimetableIdDto } from '../models' +import { GetNullableTimetableResponseDto } from '../models' // @ts-ignore import { GetTimetableByUserIdResponseDto } from '../models' // @ts-ignore @@ -606,7 +606,7 @@ const timetableTimetableIdGetFp = async ( timetableId: number, options?: AxiosRequestConfig, configuration?: Configuration, -): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> => { +): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> => { const localVarAxiosArgs = await timetableTimetableIdGetAxiosParamCreator(timetableId, options, configuration) return createRequestFunction(localVarAxiosArgs, globalAxios, configuration) } @@ -901,7 +901,7 @@ export const timetableTimetableIdGet = ({ basePath?: string axios?: AxiosInstance }) => { - return (params: TimetableTimetableIdGetRequestParams): AxiosPromise => { + return (params: TimetableTimetableIdGetRequestParams): AxiosPromise => { return timetableTimetableIdGetFp(params.timetableId, params.options, configuration).then(request => request(axios, basePath), ) diff --git a/packages/api/ku-key/models/banner-dto.ts b/packages/api/ku-key/models/banner-dto.ts index 57ddae14..a81f5071 100644 --- a/packages/api/ku-key/models/banner-dto.ts +++ b/packages/api/ku-key/models/banner-dto.ts @@ -36,4 +36,10 @@ export interface BannerDto { * @memberof BannerDto */ title: string + /** + * 배너 링크 + * @type {string} + * @memberof BannerDto + */ + link: string | null } diff --git a/packages/api/ku-key/models/get-nullable-timetable-response-dto.ts b/packages/api/ku-key/models/get-nullable-timetable-response-dto.ts new file mode 100644 index 00000000..7a30ffa0 --- /dev/null +++ b/packages/api/ku-key/models/get-nullable-timetable-response-dto.ts @@ -0,0 +1,29 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * KU-KEY API + * API for KU-KEY service + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { GetTimetableByTimetableIdDto } from './get-timetable-by-timetable-id-dto' + +/** + * + * @export + * @interface GetNullableTimetableResponseDto + */ +export interface GetNullableTimetableResponseDto { + /** + * 특정 시간표 정보 + * @type {GetTimetableByTimetableIdDto} + * @memberof GetNullableTimetableResponseDto + */ + timetable: GetTimetableByTimetableIdDto | null +} diff --git a/packages/api/ku-key/models/index.ts b/packages/api/ku-key/models/index.ts index 25b2156b..ee30fbf6 100644 --- a/packages/api/ku-key/models/index.ts +++ b/packages/api/ku-key/models/index.ts @@ -50,6 +50,7 @@ export * from './get-friend-response-dto' export * from './get-hot-club-response-dto' export * from './get-my-comment-list-response-dto' export * from './get-notice-response-dto' +export * from './get-nullable-timetable-response-dto' export * from './get-point-history-response-dto' export * from './get-post-list-response-dto' export * from './get-post-list-with-board-response-dto' diff --git a/scripts/re-create-api-json.js b/scripts/re-create-api-json.js index 8e2aca0c..dc1df067 100644 --- a/scripts/re-create-api-json.js +++ b/scripts/re-create-api-json.js @@ -37,5 +37,5 @@ async function removeOperationId(filePath) { } // 스크립트 실행 -await removeOperationId('https://15.164.27.130.nip.io/api-json') +await removeOperationId('https://dev.api.ku-key.devkor.club/api-json') await execShellCommand(`yarn prettier --w "scripts/__generated__/ku-key-api.json"`) diff --git a/src/components/ui/profile/index.tsx b/src/components/ui/profile/index.tsx index 90cc56f4..ddcde725 100644 --- a/src/components/ui/profile/index.tsx +++ b/src/components/ui/profile/index.tsx @@ -16,16 +16,19 @@ const bgConfig: Record = { interface ProfileProps extends Pick { onlyTitle: boolean bgWhite?: boolean + size?: number } -const Profile = ({ isAnonymous, isDeleted, character, onlyTitle, bgWhite }: ProfileProps) => { +const Profile = ({ isAnonymous, isDeleted, character, onlyTitle, bgWhite, size = 20 }: ProfileProps) => { const profileImg = characterConfig[character.type][character.level ?? 1] const bgColor = bgConfig[character.type] + + const w = size ?? (onlyTitle ? 15 : 20) return ( Profile ) diff --git a/src/domain/Post/components/MobileFeedItem/index.tsx b/src/domain/Post/components/MobileFeedItem/index.tsx new file mode 100644 index 00000000..19a4519d --- /dev/null +++ b/src/domain/Post/components/MobileFeedItem/index.tsx @@ -0,0 +1,152 @@ +import { useCallback, useMemo } from 'react' +import { LuBookmark, LuBookText, LuCookie, LuMessageCircle, LuMessageCircleQuestion, LuUsers } from 'react-icons/lu' +import { useNavigate } from 'react-router-dom' +import { match } from 'ts-pattern' + +import * as s from './style.css' + +import Profile from '@/components/ui/profile' +import { PostPreviewWithBoardName } from '@/packages/api/ku-key/models' +import { vars } from '@/theme/theme.css' +import { Typography } from '@/ui/Typography' + +type Props = Pick< + PostPreviewWithBoardName, + | 'id' + | 'title' + | 'content' + | 'user' + | 'reactionCount' + | 'commentCount' + | 'scrapCount' + | 'commentCount' + | 'scrapCount' + | 'myScrap' + | 'thumbnailDir' + | 'boardName' +> & { + showCommunityBadge?: boolean +} +const MobileFeedItem = ({ + id, + title, + content, + user, + reactionCount, + commentCount, + scrapCount, + myScrap, + thumbnailDir, + boardName, + showCommunityBadge, +}: Props) => { + const navigate = useNavigate() + const handleNavigate = useCallback( + () => navigate(`/community/${boardName.split(' ')[0].toLowerCase()}/post/${id}`), + [navigate, boardName, id], + ) + const svg = useMemo( + () => + match(boardName) + .with('Community Board', () => ) + .with('Question Board', () => ) + .with('Information Board', () => ) + .otherwise(() => ), + [boardName], + ) + return ( +
+
+ {thumbnailDir && ( +
+ )} +
+ {showCommunityBadge && ( +
+ {svg} + + {boardName} + +
+ )} +
+ + {title} + +
+
+ + {content} + +
+
+
+
+
+ + + {user.isAnonymous ? 'Anonymous' : user.username} + +
+
+
+ + + {reactionCount} + +
+
+ + + {commentCount} + +
+
+ + + {scrapCount} + +
+
+
+
+ ) +} + +export default MobileFeedItem diff --git a/src/domain/Post/components/MobileFeedItem/style.css.ts b/src/domain/Post/components/MobileFeedItem/style.css.ts new file mode 100644 index 00000000..26fa40aa --- /dev/null +++ b/src/domain/Post/components/MobileFeedItem/style.css.ts @@ -0,0 +1,58 @@ +import { style } from '@vanilla-extract/css' + +import { f } from '@/style' +import { vars } from '@/theme/theme.css' + +export const Wrapper = style([ + f.flex, + f.wFull, + f.directionColumn, + f.alignStart, + { + gap: '0.625rem', + paddingBottom: '1rem', + borderBottom: `1px solid ${vars.color.lightGray2}`, + selectors: { + '&:last-child': { + borderBottom: 'none', + }, + }, + }, +]) + +export const BodyWrapper = style([f.flex, f.wFull, f.alignCenter, { gap: '0.875rem' }]) + +export const Image = style([ + f.flex, + { + width: '5.625rem', + aspectRatio: '1/1', + objectFit: 'cover', + flexShrink: 0, + borderRadius: '11px', + border: `1px solid ${vars.color.lightGray1}`, + }, +]) + +export const Text = style([ + f.flex, + f.directionColumn, + f.justifyBetween, + f.alignStart, + f.wFull, + { height: '5.625rem', padding: '0.125rem 0' }, +]) + +export const Tag = style([f.flex, f.alignCenter, { gap: '0.25rem' }]) + +export const Icon = style([f.color.static.red3, { width: '0.8125rem', height: '0.8125rem' }]) + +export const TypoWrapper = style([f.wFull, f.flex, f.alignCenter]) + +export const FeedbackWrapper = style([f.flex, f.wFull, f.justifyBetween, f.alignCenter]) + +export const Profile = style([f.flex, f.alignCenter, { gap: '0.375rem' }]) + +export const FeedBack = style([f.flex, f.alignCenter, f.color.static.darkGray2, { gap: '0.625rem' }]) + +export const FeedBackIcon = style([f.flex, f.alignCenter, { gap: '0.25rem' }]) diff --git a/src/features/Community/CommunitySelectTab/index.tsx b/src/features/Community/CommunitySelectTab/index.tsx index 9f9a4a06..85497489 100644 --- a/src/features/Community/CommunitySelectTab/index.tsx +++ b/src/features/Community/CommunitySelectTab/index.tsx @@ -1,6 +1,7 @@ import * as s from './style.css' import { Button } from '@/ui/Button' +import { useMediaQueryByName } from '@/util/hooks/useMediaQueryByName' import { useQueryParams } from '@/util/hooks/useQueryParams' const boardConfig = [ @@ -34,12 +35,14 @@ export type BoardQueryParam = { const CommunitySelectTab = () => { const [queryParam, setQueryParam] = useQueryParams() - + const isMobile = useMediaQueryByName('smDown') return (
{boardConfig.map(board => (