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
1 change: 1 addition & 0 deletions src/features/upload/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./useUploadLoadingProgress";
export * from "./useUploadStepData";
export * from "./useUploadStepNavigation";
export * from "./useUploadStepProject";
Expand Down
86 changes: 86 additions & 0 deletions src/features/upload/hooks/useUploadLoadingProgress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router";

import { useUploadFlowStore } from "@/entities";
import { DYNAMIC_ROUTE_PATHS, ROUTE_PATHS } from "@/shared";

const MAX_TIMER_PROGRESS = 90;
const PROGRESS_SLOWDOWN_THRESHOLD = 70;
const FAST_PROGRESS_INCREMENT = 1.2;
const SLOW_PROGRESS_INCREMENT = 0.6;
const PROGRESS_INTERVAL_MS = 80;
const STEP_REDIRECT_DELAY_MS = 600;

const LOADING_STEPS = [
{ threshold: 95, message: "마무리 중.." },
{ threshold: 75, message: "로드맵 생성 중.." },
{ threshold: 50, message: "과제 내용 분석 중.." },
{ threshold: 20, message: "파일 텍스트 추출 중.." },
{ threshold: 0, message: "파일 업로드 중.." },
];

type Result = {
progress: number;
currentStepMessage: string;
};

export const useUploadLoadingProgress = (): Result => {
const navigate = useNavigate();
const [timerProgress, setTimerProgress] = useState(0);

const projectId = useUploadFlowStore((state) => state.projectId);
const error = useUploadFlowStore((state) => state.error);

const progress = projectId ? 100 : timerProgress;

useEffect(() => {
if (projectId) {
return;
}

const interval = setInterval(() => {
setTimerProgress((prev) => {
if (prev >= MAX_TIMER_PROGRESS) {
clearInterval(interval);
return MAX_TIMER_PROGRESS;
}

const increment =
prev < PROGRESS_SLOWDOWN_THRESHOLD
? FAST_PROGRESS_INCREMENT
: SLOW_PROGRESS_INCREMENT;

return Math.min(prev + increment, MAX_TIMER_PROGRESS);
});
}, PROGRESS_INTERVAL_MS);

return () => clearInterval(interval);
}, [projectId]);

useEffect(() => {
if (error) {
navigate(ROUTE_PATHS.FILE_UPLOAD);
}
}, [error, navigate]);

useEffect(() => {
if (!projectId) {
return;
}

const timer = setTimeout(() => {
navigate(DYNAMIC_ROUTE_PATHS.UPLOAD_STEP(projectId, 1));
}, STEP_REDIRECT_DELAY_MS);

return () => clearTimeout(timer);
}, [projectId, navigate]);

const currentStepMessage =
LOADING_STEPS.find((step) => progress >= step.threshold)?.message ??
LOADING_STEPS[LOADING_STEPS.length - 1].message;

return {
progress,
currentStepMessage,
};
};
108 changes: 108 additions & 0 deletions src/features/upload/ui/section/UploadLoadingSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Box, Flex, Image, Text, VStack } from "@chakra-ui/react";

import { SproutAnimation } from "@/features";
import AbstractBackgroundCircle from "@/shared/_assets/images/abstract-background-circle.svg";

type Props = {
progress: number;
currentStepMessage: string;
};

export const UploadLoadingSection = ({
progress,
currentStepMessage,
}: Props) => {
return (
<Flex
align="center"
bg="white"
direction="column"
h="100vh"
justify="center"
overflow="hidden"
position="relative"
>
<Box
bg="seed.subtle"
borderRadius="full"
filter="blur(32px)"
boxSize="800px"
left="50%"
opacity={0.5}
position="absolute"
top="50%"
transform="translate(-50%, -50%)"
zIndex={0}
/>

<VStack
gap={6}
left="50%"
maxW="576px"
position="absolute"
px={6}
textAlign="center"
top="50%"
transform="translate(-50%, -50%)"
w="full"
zIndex={1}
>
<Image
alt="background circle"
src={AbstractBackgroundCircle}
boxSize={30}
objectFit="contain"
/>

<SproutAnimation progress={progress} />

<VStack gap={4}>
<Text
color="neutral.900"
fontSize="4xl"
fontWeight="bold"
lineHeight="40px"
>
AI가 과제의 핵심을
<br />
분석하고 있어요
</Text>
<Text color="neutral.600" fontSize="lg" fontWeight="medium">
분석이 완료되면 나만의 로드맵이 펼쳐집니다.
</Text>
</VStack>

<VStack gap={3} maxW={80} w="full">
<Flex align="center" justify="space-between" w="full">
<Text color="seed" fontSize="xs" fontWeight="medium">
{currentStepMessage}
</Text>
<Text color="seed" fontSize="xs" fontWeight="medium">
{Math.round(progress)}%
</Text>
</Flex>

<Box
bg="neutral.300"
borderRadius="full"
h="6px"
overflow="hidden"
w="full"
>
<Box
bg="seed"
borderRadius="full"
h="full"
style={{ transition: "width 0.1s linear" }}
w={`${progress}%`}
/>
</Box>

<Text color="neutral.600" fontSize="xs" pt={2}>
잠시만 기다려주세요, 거의 다 되었습니다.
</Text>
</VStack>
</VStack>
</Flex>
);
};
1 change: 1 addition & 0 deletions src/features/upload/ui/section/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./UploadLoadingSection";
export * from "./UploadStepContentSection";
export * from "./UploadStepHeaderSection";
154 changes: 6 additions & 148 deletions src/pages/upload/_loading/UploadLoadingPage.tsx
Original file line number Diff line number Diff line change
@@ -1,154 +1,12 @@
import { useEffect, useState } from "react";
import { useNavigate } from "react-router";

import { Box, Flex, Image, Text, VStack } from "@chakra-ui/react";

import { useUploadFlowStore } from "@/entities";
import { SproutAnimation } from "@/features";
import { DYNAMIC_ROUTE_PATHS, ROUTE_PATHS } from "@/shared";
import AbstractBackgroundCircle from "@/shared/_assets/images/abstract-background-circle.svg";

const LOADING_STEPS = [
{ threshold: 0, message: "파일 업로드 중..." },
{ threshold: 20, message: "PDF 텍스트 추출 중..." },
{ threshold: 50, message: "과제 내용 분석 중..." },
{ threshold: 75, message: "로드맵 생성 중..." },
{ threshold: 95, message: "마무리 중..." },
];
import { UploadLoadingSection, useUploadLoadingProgress } from "@/features";

export default function UploadLoadingPage() {
const navigate = useNavigate();
const [timerProgress, setTimerProgress] = useState(0);

const projectId = useUploadFlowStore((state) => state.projectId);
const error = useUploadFlowStore((state) => state.error);

const progress = projectId ? 100 : timerProgress;

useEffect(() => {
const interval = setInterval(() => {
setTimerProgress((prev) => {
if (prev >= 90) {
clearInterval(interval);
return 90;
}
const increment = prev < 70 ? 1.2 : 0.6;
return Math.min(prev + increment, 90);
});
}, 80);
return () => clearInterval(interval);
}, []);

useEffect(() => {
if (error) {
navigate(ROUTE_PATHS.FILE_UPLOAD);
}
}, [error, navigate]);

useEffect(() => {
if (projectId) {
const timer = setTimeout(() => {
navigate(DYNAMIC_ROUTE_PATHS.UPLOAD_STEP(projectId, 1));
}, 600);
return () => clearTimeout(timer);
}
}, [projectId, navigate]);

const currentStep =
[...LOADING_STEPS].reverse().find((s) => progress >= s.threshold) ??
LOADING_STEPS[0];
const { progress, currentStepMessage } = useUploadLoadingProgress();

return (
<Flex
align="center"
bg="white"
direction="column"
h="100vh"
justify="center"
overflow="hidden"
position="relative"
>
<Box
bg="seed.subtle"
borderRadius="full"
filter="blur(32px)"
boxSize="800px"
left="50%"
opacity={0.5}
position="absolute"
top="50%"
transform="translate(-50%, -50%)"
zIndex={0}
/>

<VStack
gap={6}
left="50%"
maxW="576px"
position="absolute"
px={6}
textAlign="center"
top="50%"
transform="translate(-50%, -50%)"
w="full"
zIndex={1}
>
<Image
alt="background circle"
src={AbstractBackgroundCircle}
boxSize={30}
objectFit="contain"
/>

<SproutAnimation progress={progress} />

<VStack gap={4}>
<Text
color="neutral.900"
fontSize="4xl"
fontWeight="bold"
lineHeight="40px"
>
AI가 과제의 핵심을
<br />
분석하고 있어요
</Text>
<Text color="neutral.600" fontSize="lg" fontWeight="medium">
분석이 완료되면 나만의 로드맵이 펼쳐집니다.
</Text>
</VStack>

<VStack gap={3} maxW={80} w="full">
<Flex align="center" justify="space-between" w="full">
<Text color="seed" fontSize="xs" fontWeight="medium">
{currentStep.message}
</Text>
<Text color="seed" fontSize="xs" fontWeight="medium">
{Math.round(progress)}%
</Text>
</Flex>

<Box
bg="neutral.300"
borderRadius="full"
h="6px"
overflow="hidden"
w="full"
>
<Box
bg="seed"
borderRadius="full"
h="full"
style={{ transition: "width 0.1s linear" }}
w={`${progress}%`}
/>
</Box>

<Text color="neutral.600" fontSize="xs" pt={2}>
잠시만 기다려주세요, 거의 다 되었습니다.
</Text>
</VStack>
</VStack>
</Flex>
<UploadLoadingSection
currentStepMessage={currentStepMessage}
progress={progress}
/>
);
}
Loading