diff --git a/apps/backend/src/docker/docker.service.ts b/apps/backend/src/docker/docker.service.ts index bf85b5ef..23b1ed38 100644 --- a/apps/backend/src/docker/docker.service.ts +++ b/apps/backend/src/docker/docker.service.ts @@ -99,7 +99,7 @@ export class DockerService { }); stream.on('end', async () => { - // await container.remove({ force: true }); + await container.remove({ force: true }); let result = this.filterAnsiCode(output); if (inputs.length !== 0) { result = result.split('\n').slice(1).join('\n'); diff --git "a/apps/backend/src/filter/\btypeorm.exception.filter.ts" b/apps/backend/src/filter/typeorm.exception.filter.ts similarity index 100% rename from "apps/backend/src/filter/\btypeorm.exception.filter.ts" rename to apps/backend/src/filter/typeorm.exception.filter.ts diff --git a/apps/backend/src/lotus/dto/lotus.detail.dto.ts b/apps/backend/src/lotus/dto/lotus.detail.dto.ts index 04350ed8..8696e922 100644 --- a/apps/backend/src/lotus/dto/lotus.detail.dto.ts +++ b/apps/backend/src/lotus/dto/lotus.detail.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { IsArray, IsBoolean, IsDate, IsString, ValidateNested } from 'class-validator'; +import { IsArray, IsBoolean, IsDate, IsString, IsUrl, ValidateNested } from 'class-validator'; import { SimpleFileResponseDto } from './simple.file.response.dto'; import { SimpleUserResponseDto } from './simple.user.response.dto'; import { GistApiFileListDto } from '@/gist/dto/gistApiFileList.dto'; @@ -43,6 +43,12 @@ export class LotusDetailDto { }) version: string; + @IsUrl() + @ApiProperty({ + example: 'https://gist.github.com/gistId~~/commitId~~' + }) + gistUrl: string; + @IsBoolean() @ApiProperty({ example: true @@ -73,6 +79,7 @@ export class LotusDetailDto { id: lotus.lotusId, title: lotus.title, language: lotus.language, + gistUrl: `https://gist.github.com/${lotus.gistRepositoryId}/${lotus.commitId}`, version: lotus.version, date: lotus.createdAt, isPublic: lotus.isPublic, diff --git a/apps/backend/src/lotus/dto/lotus.response.dto.ts b/apps/backend/src/lotus/dto/lotus.response.dto.ts index ca378e7e..4676961d 100644 --- a/apps/backend/src/lotus/dto/lotus.response.dto.ts +++ b/apps/backend/src/lotus/dto/lotus.response.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { IsBoolean, IsDate, IsString, ValidateNested } from 'class-validator'; +import { IsBoolean, IsDate, IsString, IsUrl, ValidateNested } from 'class-validator'; import { SimpleUserResponseDto } from './simple.user.response.dto'; import { Lotus } from '@/lotus/lotus.entity'; @@ -29,6 +29,12 @@ export class LotusResponseDto { }) language: string; + @IsUrl() + @ApiProperty({ + example: 'https://gist.github.com/gistId~~/commitId~~' + }) + gistUrl: string; + @IsDate() @ApiProperty({ example: '로투스 생성 일시' @@ -54,6 +60,7 @@ export class LotusResponseDto { author: user, title: lotus.title, language: lotus.language, + gistUrl: `https://gist.github.com/${lotus.gistRepositoryId}/${lotus.commitId}`, isPublic: lotus.isPublic, date: lotus.createdAt, tags @@ -67,6 +74,7 @@ export class LotusResponseDto { id: lotus.lotusId, author: simpleUser, title: lotus.title, + gistUrl: `https://gist.github.com/${lotus.gistRepositoryId}/${lotus.commitId}`, language: lotus.language, isPublic: lotus.isPublic, date: lotus.createdAt, diff --git a/apps/backend/src/lotus/dto/simple.user.response.dto.ts b/apps/backend/src/lotus/dto/simple.user.response.dto.ts index 831db434..6e3e204d 100644 --- a/apps/backend/src/lotus/dto/simple.user.response.dto.ts +++ b/apps/backend/src/lotus/dto/simple.user.response.dto.ts @@ -21,11 +21,18 @@ export class SimpleUserResponseDto { }) profile: string; + @IsUrl() + @ApiProperty({ + example: 'https://gist.github.com/nickname' + }) + gistUrl: string; + static ofUserDto(userData: User) { return { id: userData.userId, nickname: userData.nickname, - profile: userData.profilePath + profile: userData.profilePath, + gistUrl: userData.gistUrl }; } } diff --git a/apps/backend/src/lotus/lotus.service.ts b/apps/backend/src/lotus/lotus.service.ts index 0e3cf26f..8732a305 100644 --- a/apps/backend/src/lotus/lotus.service.ts +++ b/apps/backend/src/lotus/lotus.service.ts @@ -132,9 +132,7 @@ export class LotusService { } async getPublicLotus(page: number, size: number, search: string): Promise { - const lotusData = await this.getLotusByTags(search); - - const totalNum = lotusData.length; + const [lotusData, totalNum] = await this.getLotusByTags(page, size, search); const maxPage = Math.ceil(totalNum / size); if (page > maxPage && maxPage !== 0) { throw new HttpException('page must be lower than max page', HttpStatus.NOT_FOUND); @@ -142,44 +140,49 @@ export class LotusService { if (page <= 0) { throw new HttpException('page must be higher than 0', HttpStatus.NOT_FOUND); } - const firstIdx = size * (page - 1); - const returnLotusData = lotusData.splice(firstIdx, size); - - return LotusPublicDto.ofLotusList(returnLotusData, page, maxPage); + return LotusPublicDto.ofLotusList(lotusData, page, maxPage); } - async getLotusByTags(search: string) { + async getLotusByTags(page: number, size: number, search: string) { if (!search) { - return await this.lotusRepository.find({ + return await this.lotusRepository.findAndCount({ where: { isPublic: true }, - relations: ['tags', 'user'] + skip: (page - 1) * size, + take: size, + relations: ['tags', 'user'], + order: { createdAt: 'DESC' } }); } const tags = await this.tagService.searchTag(search); - return await this.lotusRepository.find({ + return await this.lotusRepository.findAndCount({ where: { isPublic: true, tags: { tagId: In(tags) } }, - relations: ['tags', 'user'] + skip: (page - 1) * size, + take: size, + relations: ['tags', 'user'], + order: { createdAt: 'DESC' } }); } async getUserLotus(userId: string, page: number, size: number) { const user = this.userService.findOneByUserId(userId); if (!user) { - throw new HttpException('user data is not found', HttpStatus.NOT_FOUND); + throw new HttpException('user data is not found', HttpStatus.UNAUTHORIZED); } - const lotusData = await this.lotusRepository.find({ + const [lotusData, totalNum] = await this.lotusRepository.findAndCount({ where: { user: { userId } }, - relations: ['tags', 'user'] + skip: (page - 1) * size, + take: size, + relations: ['tags', 'user'], + order: { createdAt: 'DESC' } }); - const totalNum = lotusData.length; const maxPage = Math.ceil(totalNum / size); if (page > maxPage && maxPage !== 0) { throw new HttpException('page must be lower than max page', HttpStatus.NOT_FOUND); @@ -187,10 +190,7 @@ export class LotusService { if (page <= 0) { throw new HttpException('page must be higher than 0', HttpStatus.NOT_FOUND); } - const firstIdx = size * (page - 1); - const returnLotusData = lotusData.splice(firstIdx, size); - - return LotusPublicDto.ofLotusList(returnLotusData, page, maxPage); + return LotusPublicDto.ofLotusList(lotusData, page, maxPage); } async checkAlreadyExist(gistUuid: string, commitId: string) { diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index 9ba573d4..5479d20d 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -2,8 +2,8 @@ import { INestApplication, ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerCustomOptions, SwaggerModule } from '@nestjs/swagger'; import { AppModule } from './app.module'; -import { typeOrmExceptionFilter } from './filter/\btypeorm.exception.filter'; import { httpExceptionFilter } from './filter/http.exception.filter'; +import { typeOrmExceptionFilter } from './filter/typeorm.exception.filter'; async function bootstrap() { const app: INestApplication = await NestFactory.create(AppModule); diff --git a/apps/backend/src/user/dto/user.create.dto.ts b/apps/backend/src/user/dto/user.create.dto.ts index cf52fc2f..7df0da94 100644 --- a/apps/backend/src/user/dto/user.create.dto.ts +++ b/apps/backend/src/user/dto/user.create.dto.ts @@ -19,6 +19,9 @@ export class UserCreateDto { @IsNumber() gitId: number; + @IsString() + gistUrl: string; + @ValidateNested({ each: true }) @Type(() => Lotus) lotuses: Lotus[]; @@ -32,6 +35,7 @@ export class UserCreateDto { this.profilePath = response.avatar_url; this.gitToken = accessToken; this.gitId = response.id; + this.gistUrl = `https://gist.github.com/${response.login}`; this.lotuses = []; this.comments = []; } diff --git a/apps/backend/src/user/user.entity.ts b/apps/backend/src/user/user.entity.ts index 80b1426e..cc4bca8a 100644 --- a/apps/backend/src/user/user.entity.ts +++ b/apps/backend/src/user/user.entity.ts @@ -17,6 +17,9 @@ export class User { @Column({ name: 'git_token' }) gitToken: string; + @Column({ name: 'gist_url' }) + gistUrl: string; + @Column({ name: 'git_id', unique: true }) gitId: number; diff --git a/apps/frontend/src/app/router/routeTree.gen.ts b/apps/frontend/src/app/router/routeTree.gen.ts index 2dfab397..af409194 100644 --- a/apps/frontend/src/app/router/routeTree.gen.ts +++ b/apps/frontend/src/app/router/routeTree.gen.ts @@ -18,12 +18,12 @@ import { Route as LoginErrorIndexImport } from './../../page/login/error/index'; import { Route as mainUserIndexImport } from './../../page/(main)/user/index'; import { Route as mainLotusIndexImport } from './../../page/(main)/lotus/index'; import { Route as mainLotusCreateIndexImport } from './../../page/(main)/lotus/create/index'; +import { Route as mainLotusLotusIdIndexImport } from './../../page/(main)/lotus/$lotusId/index'; // Create Virtual Routes const mainRouteLazyImport = createFileRoute('/(main)')(); const IndexLazyImport = createFileRoute('/')(); -const mainLotusLotusIdIndexLazyImport = createFileRoute('/(main)/lotus/$lotusId/')(); // Create/Update Routes @@ -69,14 +69,6 @@ const mainLotusIndexRoute = mainLotusIndexImport } as any) .lazy(() => import('./../../page/(main)/lotus/index.lazy').then((d) => d.Route)); -const mainLotusLotusIdIndexLazyRoute = mainLotusLotusIdIndexLazyImport - .update({ - id: '/lotus/$lotusId/', - path: '/lotus/$lotusId/', - getParentRoute: () => mainRouteLazyRoute - } as any) - .lazy(() => import('./../../page/(main)/lotus/$lotusId/index.lazy').then((d) => d.Route)); - const mainLotusCreateIndexRoute = mainLotusCreateIndexImport .update({ id: '/lotus/create/', @@ -85,6 +77,14 @@ const mainLotusCreateIndexRoute = mainLotusCreateIndexImport } as any) .lazy(() => import('./../../page/(main)/lotus/create/index.lazy').then((d) => d.Route)); +const mainLotusLotusIdIndexRoute = mainLotusLotusIdIndexImport + .update({ + id: '/lotus/$lotusId/', + path: '/lotus/$lotusId/', + getParentRoute: () => mainRouteLazyRoute + } as any) + .lazy(() => import('./../../page/(main)/lotus/$lotusId/index.lazy').then((d) => d.Route)); + // Populate the FileRoutesByPath interface declare module '@tanstack/react-router' { @@ -131,6 +131,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginSuccessIndexImport; parentRoute: typeof rootRoute; }; + '/(main)/lotus/$lotusId/': { + id: '/(main)/lotus/$lotusId/'; + path: '/lotus/$lotusId'; + fullPath: '/lotus/$lotusId'; + preLoaderRoute: typeof mainLotusLotusIdIndexImport; + parentRoute: typeof mainRouteLazyImport; + }; '/(main)/lotus/create/': { id: '/(main)/lotus/create/'; path: '/lotus/create'; @@ -138,13 +145,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof mainLotusCreateIndexImport; parentRoute: typeof mainRouteLazyImport; }; - '/(main)/lotus/$lotusId/': { - id: '/(main)/lotus/$lotusId/'; - path: '/lotus/$lotusId'; - fullPath: '/lotus/$lotusId'; - preLoaderRoute: typeof mainLotusLotusIdIndexLazyImport; - parentRoute: typeof mainRouteLazyImport; - }; } } @@ -153,15 +153,15 @@ declare module '@tanstack/react-router' { interface mainRouteLazyRouteChildren { mainLotusIndexRoute: typeof mainLotusIndexRoute; mainUserIndexRoute: typeof mainUserIndexRoute; + mainLotusLotusIdIndexRoute: typeof mainLotusLotusIdIndexRoute; mainLotusCreateIndexRoute: typeof mainLotusCreateIndexRoute; - mainLotusLotusIdIndexLazyRoute: typeof mainLotusLotusIdIndexLazyRoute; } const mainRouteLazyRouteChildren: mainRouteLazyRouteChildren = { mainLotusIndexRoute: mainLotusIndexRoute, mainUserIndexRoute: mainUserIndexRoute, - mainLotusCreateIndexRoute: mainLotusCreateIndexRoute, - mainLotusLotusIdIndexLazyRoute: mainLotusLotusIdIndexLazyRoute + mainLotusLotusIdIndexRoute: mainLotusLotusIdIndexRoute, + mainLotusCreateIndexRoute: mainLotusCreateIndexRoute }; const mainRouteLazyRouteWithChildren = mainRouteLazyRoute._addFileChildren(mainRouteLazyRouteChildren); @@ -172,8 +172,8 @@ export interface FileRoutesByFullPath { '/user': typeof mainUserIndexRoute; '/login/error': typeof LoginErrorIndexRoute; '/login/success': typeof LoginSuccessIndexRoute; + '/lotus/$lotusId': typeof mainLotusLotusIdIndexRoute; '/lotus/create': typeof mainLotusCreateIndexRoute; - '/lotus/$lotusId': typeof mainLotusLotusIdIndexLazyRoute; } export interface FileRoutesByTo { @@ -182,8 +182,8 @@ export interface FileRoutesByTo { '/user': typeof mainUserIndexRoute; '/login/error': typeof LoginErrorIndexRoute; '/login/success': typeof LoginSuccessIndexRoute; + '/lotus/$lotusId': typeof mainLotusLotusIdIndexRoute; '/lotus/create': typeof mainLotusCreateIndexRoute; - '/lotus/$lotusId': typeof mainLotusLotusIdIndexLazyRoute; } export interface FileRoutesById { @@ -194,15 +194,15 @@ export interface FileRoutesById { '/(main)/user/': typeof mainUserIndexRoute; '/login/error/': typeof LoginErrorIndexRoute; '/login/success/': typeof LoginSuccessIndexRoute; + '/(main)/lotus/$lotusId/': typeof mainLotusLotusIdIndexRoute; '/(main)/lotus/create/': typeof mainLotusCreateIndexRoute; - '/(main)/lotus/$lotusId/': typeof mainLotusLotusIdIndexLazyRoute; } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath; - fullPaths: '/' | '/lotus' | '/user' | '/login/error' | '/login/success' | '/lotus/create' | '/lotus/$lotusId'; + fullPaths: '/' | '/lotus' | '/user' | '/login/error' | '/login/success' | '/lotus/$lotusId' | '/lotus/create'; fileRoutesByTo: FileRoutesByTo; - to: '/' | '/lotus' | '/user' | '/login/error' | '/login/success' | '/lotus/create' | '/lotus/$lotusId'; + to: '/' | '/lotus' | '/user' | '/login/error' | '/login/success' | '/lotus/$lotusId' | '/lotus/create'; id: | '__root__' | '/' @@ -211,8 +211,8 @@ export interface FileRouteTypes { | '/(main)/user/' | '/login/error/' | '/login/success/' - | '/(main)/lotus/create/' - | '/(main)/lotus/$lotusId/'; + | '/(main)/lotus/$lotusId/' + | '/(main)/lotus/create/'; fileRoutesById: FileRoutesById; } @@ -252,8 +252,8 @@ export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileT "children": [ "/(main)/lotus/", "/(main)/user/", - "/(main)/lotus/create/", - "/(main)/lotus/$lotusId/" + "/(main)/lotus/$lotusId/", + "/(main)/lotus/create/" ] }, "/(main)/lotus/": { @@ -270,12 +270,12 @@ export const routeTree = rootRoute._addFileChildren(rootRouteChildren)._addFileT "/login/success/": { "filePath": "login/success/index.tsx" }, - "/(main)/lotus/create/": { - "filePath": "(main)/lotus/create/index.tsx", + "/(main)/lotus/$lotusId/": { + "filePath": "(main)/lotus/$lotusId/index.tsx", "parent": "/(main)" }, - "/(main)/lotus/$lotusId/": { - "filePath": "(main)/lotus/$lotusId/index.lazy.tsx", + "/(main)/lotus/create/": { + "filePath": "(main)/lotus/create/index.tsx", "parent": "/(main)" } } diff --git a/apps/frontend/src/feature/history/api.ts b/apps/frontend/src/feature/history/api.ts index e9f27837..4eeea5b5 100644 --- a/apps/frontend/src/feature/history/api.ts +++ b/apps/frontend/src/feature/history/api.ts @@ -2,16 +2,16 @@ import { HistoryType } from '.'; import { api } from '@/shared/common/api'; export const getLotusHistoryList = async ({ - id + id, + page = 1 }: { id: string; + page?: number; }): Promise<{ list: HistoryType[]; page: { current: number; max: number } }> => { - const response = await api.get(`/api/lotus/${id}/history`); + const response = await api.get(`/api/lotus/${id}/history?page=${page}&size=${5}`); const data = response.data as { list: HistoryType[]; page: { current: number; max: number } }; - console.log(data); - const body = { ...data, list: data.list.map(({ date, ...rest }) => ({ ...rest, date: new Date(date) })) diff --git a/apps/frontend/src/page/(main)/lotus/$lotusId/index.lazy.tsx b/apps/frontend/src/page/(main)/lotus/$lotusId/index.lazy.tsx index 05fa3c0a..55a04faa 100644 --- a/apps/frontend/src/page/(main)/lotus/$lotusId/index.lazy.tsx +++ b/apps/frontend/src/page/(main)/lotus/$lotusId/index.lazy.tsx @@ -1,9 +1,11 @@ -import { createLazyFileRoute } from '@tanstack/react-router'; +import { createLazyFileRoute, getRouteApi } from '@tanstack/react-router'; +import { lotusHistoryQueryOptions } from '@/feature/history/query'; import { AsyncBoundary } from '@/shared/boundary'; import { SuspenseLotusHistoryList } from '@/widget/history'; import { CodeRunButton } from '@/widget/lotusCodeRun'; import { SuspenseLotusDetail } from '@/widget/lotusDetail'; import { SuspenseLotusFiles } from '@/widget/lotusDetail/SuspenseLotusFiles'; +import { SuspensePagination } from '@/widget/SuspensePagination'; import '@/app/style/github.css'; @@ -11,8 +13,18 @@ export const Route = createLazyFileRoute('/(main)/lotus/$lotusId/')({ component: RouteComponent }); +const { useSearch, useNavigate, useParams } = getRouteApi('/(main)/lotus/$lotusId/'); + function RouteComponent() { - const { lotusId: id } = Route.useParams(); + const { lotusId: id } = useParams(); + + const { page = 1 } = useSearch(); + + const navigate = useNavigate(); + + const handleChangePage = (page: number) => { + navigate({ search: { page } }); + }; return (
@@ -27,7 +39,14 @@ function RouteComponent() { Loading...
} rejected={() =>
Error!
}> - + + + + Loading...} rejected={() =>
Error!
}> +
); diff --git a/apps/frontend/src/page/(main)/lotus/$lotusId/index.tsx b/apps/frontend/src/page/(main)/lotus/$lotusId/index.tsx new file mode 100644 index 00000000..cbed9f15 --- /dev/null +++ b/apps/frontend/src/page/(main)/lotus/$lotusId/index.tsx @@ -0,0 +1,10 @@ +import { createFileRoute } from '@tanstack/react-router'; +import { z } from 'zod'; + +const historySearchPageValidate = z.object({ + page: z.number().default(1).optional() +}); + +export const Route = createFileRoute('/(main)/lotus/$lotusId/')({ + validateSearch: (search) => historySearchPageValidate.parse(search) +}); diff --git a/apps/frontend/src/shared/pagination/Pagination.tsx b/apps/frontend/src/shared/pagination/Pagination.tsx index 36e3859a..59a40c8c 100644 --- a/apps/frontend/src/shared/pagination/Pagination.tsx +++ b/apps/frontend/src/shared/pagination/Pagination.tsx @@ -15,13 +15,15 @@ interface PaginationProps { totalPages?: number; initialPage?: number; onChangePage?: (page: number) => void; + activeScrollTop?: true; } -export function Pagination({ totalPages = 1, initialPage = 1, onChangePage }: PaginationProps) { +export function Pagination({ totalPages = 1, initialPage = 1, onChangePage, activeScrollTop }: PaginationProps) { const { currentPage, onClickPage, onClickPrevious, onClickNext, getPaginationItems } = usePagination({ totalPages, initialPage, - onChangePage + onChangePage, + activeScrollTop }); return ( diff --git a/apps/frontend/src/shared/pagination/usePagination.tsx b/apps/frontend/src/shared/pagination/usePagination.tsx index 1be14e19..10bf291c 100644 --- a/apps/frontend/src/shared/pagination/usePagination.tsx +++ b/apps/frontend/src/shared/pagination/usePagination.tsx @@ -4,15 +4,17 @@ interface UsePaginationProps { totalPages: number; initialPage?: number; onChangePage?: (page: number) => void; + activeScrollTop?: true; } -export function usePagination({ totalPages, initialPage = 1, onChangePage }: UsePaginationProps) { +export function usePagination({ totalPages, initialPage = 1, onChangePage, activeScrollTop }: UsePaginationProps) { const [currentPage, setCurrentPage] = useState(initialPage); const onClickPage = (page: number) => { setCurrentPage(page); onChangePage?.(page); - window.scrollTo({ top: 0, behavior: 'smooth' }); + + if (activeScrollTop) window.scrollTo({ top: 0, behavior: 'smooth' }); }; const onClickPrevious = () => { diff --git a/apps/frontend/src/widget/SuspensePagination.tsx b/apps/frontend/src/widget/SuspensePagination.tsx new file mode 100644 index 00000000..30eca5d5 --- /dev/null +++ b/apps/frontend/src/widget/SuspensePagination.tsx @@ -0,0 +1,33 @@ +import { useSuspenseQuery } from '@tanstack/react-query'; +import { Pagination } from '@/shared/pagination'; + +interface Page { + max: number; + current: number; +} + +interface PaginationQueryOptions { + queryKey: Record[]; + queryFn: (...args: any[]) => Promise<{ page: Page }>; +} + +interface SuspensePaginationProps { + queryOptions: PaginationQueryOptions; + onChangePage: (page: number) => void; + activeScrollTop?: true; +} + +export function SuspensePagination({ queryOptions, onChangePage, activeScrollTop }: SuspensePaginationProps) { + const { data } = useSuspenseQuery(queryOptions); + + return ( + + ); +} + +SuspensePagination.Skeleton = Pagination.Skeleton; diff --git a/apps/frontend/src/widget/history/SuspenseLotusHistoryDetail.tsx b/apps/frontend/src/widget/history/SuspenseLotusHistoryDetail.tsx index 0fe89bb5..119adfb9 100644 --- a/apps/frontend/src/widget/history/SuspenseLotusHistoryDetail.tsx +++ b/apps/frontend/src/widget/history/SuspenseLotusHistoryDetail.tsx @@ -1,8 +1,8 @@ import { useEffect } from 'react'; -import { Text } from '@froxy/design/components'; +import { Button, Heading } from '@froxy/design/components'; import { Markdown } from '@froxy/react-markdown'; -import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query'; -import { HistoryType } from '@/feature/history'; +import { useQueryClient, useQueryErrorResetBoundary, useSuspenseQuery } from '@tanstack/react-query'; +import { AiOutlineLoading } from 'react-icons/ai'; import { lotusHistoryQueryOptions } from '@/feature/history/query'; export function SuspenseLotusHistoryDetail({ lotusId, historyId }: { lotusId: string; historyId: string }) { @@ -17,9 +17,7 @@ export function SuspenseLotusHistoryDetail({ lotusId, historyId }: { lotusId: st if (data.status !== 'PENDING') return; const interval = setInterval(async () => { - await queryClient.refetchQueries(lotusDetailQueryOptions); - - const updatedData = queryClient.getQueryData(lotusDetailQueryOptions.queryKey); + const updatedData = await queryClient.fetchQuery(lotusDetailQueryOptions); if (!updatedData || updatedData.status === 'PENDING') return; @@ -32,15 +30,49 @@ export function SuspenseLotusHistoryDetail({ lotusId, historyId }: { lotusId: st const terminal = '```bash\n' + data.output + '\n```'; if (data.status === 'PENDING') { - return ( -
- PENDING... -
- ); + return ; } + return ( -
+
); } + +function LotusHistoryDetailFallback({ title }: { title: string }) { + return ( +
+ + {title} +
+ ); +} + +SuspenseLotusHistoryDetail.Fallback = LotusHistoryDetailFallback; + +function LotusHistoryDetailError({ retry }: { retry?: () => void }) { + const { reset } = useQueryErrorResetBoundary(); + + const handleClick = () => { + reset(); + retry?.(); + }; + + return ( +
+
+ + OOPS +
+ + {retry && ( + + )} +
+ ); +} + +SuspenseLotusHistoryDetail.Error = LotusHistoryDetailError; diff --git a/apps/frontend/src/widget/history/SuspenseLotusHistoryList.tsx b/apps/frontend/src/widget/history/SuspenseLotusHistoryList.tsx index 3695cc07..b4d829d5 100644 --- a/apps/frontend/src/widget/history/SuspenseLotusHistoryList.tsx +++ b/apps/frontend/src/widget/history/SuspenseLotusHistoryList.tsx @@ -5,27 +5,37 @@ import { SuspenseLotusHistoryDetail } from './SuspenseLotusHistoryDetail'; import { lotusHistoryQueryOptions } from '@/feature/history/query'; import { AsyncBoundary } from '@/shared/boundary'; -export function SuspenseLotusHistoryList({ id }: { id: string }) { +export function SuspenseLotusHistoryList({ id, page = 1 }: { id: string; page?: number }) { const { data: { list } - } = useSuspenseQuery(lotusHistoryQueryOptions.list({ id })); + } = useSuspenseQuery(lotusHistoryQueryOptions.list({ id, page })); - const firstPendingIndex = list.findIndex((history) => history.status === 'PENDING'); + const pendingHistoriesId = list.filter((history) => history.status === 'PENDING').map((history) => history.id); + + const firstPageFirstItem = page === 1 ? [list[0].id] : []; return ( -
- +
+ {/* NOTE : key를 이용해 갱신해야 Pending 상태인 Content 고정 가능 */} + {list.map((history) => ( - + - Loading...
} rejected={() =>
Error!!
}> + } + rejected={({ retry }) => } + > diff --git a/apps/frontend/src/widget/lotusCodeRun/CodeRunButton.tsx b/apps/frontend/src/widget/lotusCodeRun/CodeRunButton.tsx index 25b9412f..dea6a44d 100644 --- a/apps/frontend/src/widget/lotusCodeRun/CodeRunButton.tsx +++ b/apps/frontend/src/widget/lotusCodeRun/CodeRunButton.tsx @@ -22,8 +22,9 @@ export function CodeRunButton({ lotusId }: { lotusId: string }) { { lotusId, input: inputs, execFileName }, { onSuccess: () => { - queryClient.invalidateQueries(lotusHistoryQueryOptions.list({ id: lotusId })); toast({ description: '코드가 실행되었습니다.', variant: 'success', duration: 2000 }); + + queryClient.invalidateQueries(lotusHistoryQueryOptions.list({ id: lotusId })); }, onError: () => { toast({ diff --git a/apps/frontend/src/widget/lotusCodeRun/LotusRunCodeForm.tsx b/apps/frontend/src/widget/lotusCodeRun/LotusRunCodeForm.tsx index 48aa722e..0b2a692d 100644 --- a/apps/frontend/src/widget/lotusCodeRun/LotusRunCodeForm.tsx +++ b/apps/frontend/src/widget/lotusCodeRun/LotusRunCodeForm.tsx @@ -15,8 +15,6 @@ export function LotusRunCodeForm({ lotusId, onCancel, onSubmit }: LotusRunCodeFo const [inputs, setInputs] = useState([]); const handleSubmit = () => { - console.log(execFileName, inputs); - onSubmit?.({ execFileName, inputs }); }; diff --git a/packages/react-markdown/src/github.css b/packages/react-markdown/src/github.css index 88fc2381..7d39d0d3 100644 --- a/packages/react-markdown/src/github.css +++ b/packages/react-markdown/src/github.css @@ -152,5 +152,5 @@ code span[data-highlighted-line-id='rm'] { } .terminal code > [data-line]::before { - content: '>'; + content: '❯'; }