Skip to content

Commit e8873f1

Browse files
authored
Merge pull request #309 from edu-pi/main
Main
2 parents b851d47 + 4c19b02 commit e8873f1

Some content is hidden

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

58 files changed

+1934
-949
lines changed

README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
# Haeya 팀 프론트
22

3-
실행 하는 방법
3+
## 실행 방법
44

5-
```
5+
### 1. 의존성 설치
6+
7+
```bash
68
npm i
79
```
810

11+
### 2. 환경변수 설정
12+
13+
프로젝트 루트에 `.env.development` 파일을 생성하고 다음 내용을 추가하세요:
14+
15+
```env
16+
# msw 사용
17+
VITE_APP_NODE_ENV=development
18+
VITE_APP_USE_MSW=true
919
```
20+
21+
### 3. 개발 서버 실행
22+
23+
```bash
1024
npm run dev
1125
```

src/App.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1929,6 +1929,29 @@ gpt-icon-small {
19291929
z-index: 9999;
19301930
}
19311931

1932+
.tutorial-close-button {
1933+
position: absolute;
1934+
top: 10px;
1935+
right: 10px;
1936+
background: none;
1937+
border: none;
1938+
font-size: 28px;
1939+
color: #666;
1940+
cursor: pointer;
1941+
width: 30px;
1942+
height: 30px;
1943+
display: flex;
1944+
align-items: center;
1945+
justify-content: center;
1946+
padding: 0;
1947+
line-height: 1;
1948+
transition: color 0.2s;
1949+
}
1950+
1951+
.tutorial-close-button:hover {
1952+
color: #333;
1953+
}
1954+
19321955
.tutorial-content h2 {
19331956
margin-bottom: 15px;
19341957
color: #333;

src/App.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,15 @@ function App() {
3232
useEffect(() => {
3333
async function initializeMSW() {
3434
if (typeof window !== "undefined") {
35-
if (import.meta.env.VITE_APP_NODE_ENV === "development") {
35+
// 환경변수에 따라 MSW 활성화 여부 결정
36+
const shouldUseMSW = import.meta.env.VITE_APP_USE_MSW === "true";
37+
38+
if (shouldUseMSW) {
3639
await setupMSW();
40+
} else {
41+
console.log("MSW disabled - Using real API server");
3742
}
43+
3844
setIsMswReady(true);
3945
}
4046
}

src/mocks/handlers.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { http, HttpResponse, delay } from "msw";
22
import * as jose from "jose";
3-
import testResponseBody from "./samples/testResponseBody";
3+
// import testResponseBody from "./samples/testResponseBody";
4+
import forLoopResponseBody from "./samples/forLoopResponseBody";
45

56
const JWT_SECRET = new TextEncoder().encode("your_jwt_secret_key");
67

@@ -27,21 +28,36 @@ interface SignupUser {
2728
}
2829

2930
export const handlers = [
30-
// //시각화 요청 성공
31-
http.post("http://localhost:8080/edupi-assist/v1/execute/visualize", () => {
31+
http.post("*/edupi-assist/v1/execute/visualize", () => {
3232
return HttpResponse.json(
3333
{
34-
success: true,
35-
code: "CS-200000",
34+
code: "CA-200000",
3635
detail: "success code analysis",
37-
result: { code: testResponseBody },
36+
result: {
37+
code: forLoopResponseBody,
38+
},
3839
},
3940
{
4041
status: 200,
4142
}
4243
);
4344
}),
4445

46+
// 기존 테스트 응답 (주석 처리 - 필요시 활성화)
47+
// http.post("http://localhost:8080/edupi-assist/v1/execute/visualize", () => {
48+
// return HttpResponse.json(
49+
// {
50+
// success: true,
51+
// code: "CS-200000",
52+
// detail: "success code analysis",
53+
// result: { code: testResponseBody },
54+
// },
55+
// {
56+
// status: 200,
57+
// }
58+
// );
59+
// }),
60+
4561
// 시각화 요청 실패
4662
// http.post("http://localhost:8080/edupi-assist/v1/execute/visualize", async () => {
4763
// return HttpResponse.json(
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// for 루프 시각화 테스트용 응답 데이터
2+
const forLoopResponseBody = [
3+
{
4+
id: 1,
5+
depth: 1,
6+
condition: {
7+
target: "i",
8+
cur: "0",
9+
start: "0",
10+
end: "3",
11+
step: "1",
12+
},
13+
highlights: ["target", "cur", "start", "end", "step"],
14+
code: "for i in range(3):",
15+
type: "for",
16+
},
17+
{
18+
id: 2,
19+
depth: 2,
20+
expr: "1",
21+
highlights: [0],
22+
console: "1\n",
23+
code: "print(1)",
24+
type: "print",
25+
},
26+
{
27+
id: 1,
28+
depth: 1,
29+
condition: {
30+
target: "i",
31+
cur: "1",
32+
start: "0",
33+
end: "3",
34+
step: "1",
35+
},
36+
highlights: ["cur"],
37+
code: "for i in range(3):",
38+
type: "for",
39+
},
40+
{
41+
id: 2,
42+
depth: 2,
43+
expr: "1",
44+
highlights: [0],
45+
console: "1\n",
46+
code: "print(1)",
47+
type: "print",
48+
},
49+
{
50+
id: 1,
51+
depth: 1,
52+
condition: {
53+
target: "i",
54+
cur: "2",
55+
start: "0",
56+
end: "3",
57+
step: "1",
58+
},
59+
highlights: ["cur"],
60+
code: "for i in range(3):",
61+
type: "for",
62+
},
63+
{
64+
id: 2,
65+
depth: 2,
66+
expr: "1",
67+
highlights: [0],
68+
console: "1\n",
69+
code: "print(1)",
70+
type: "print",
71+
},
72+
];
73+
74+
export default forLoopResponseBody;
75+

src/mocks/setup.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
// src/mocks/setup.ts
21
import { worker } from "./browser";
32

43
export const setupMSW = async () => {
5-
if (import.meta.env.VITE_APP_NODE_ENV === "development") {
6-
try {
7-
await worker.start({
8-
onUnhandledRequest: "bypass",
9-
serviceWorker: {
10-
url: `${window.location.origin}/mockServiceWorker.js`,
11-
},
12-
});
13-
console.log("MSW started successfully");
14-
} catch (error) {
15-
console.error("Failed to start MSW:", error);
16-
}
4+
const shouldUseMSW = import.meta.env.VITE_APP_USE_MSW === "true";
5+
6+
if (!shouldUseMSW) {
7+
console.log("MSW is disabled. Using real API server.");
8+
return;
9+
}
10+
11+
try {
12+
await worker.start({
13+
onUnhandledRequest: "bypass",
14+
serviceWorker: {
15+
url: `${window.location.origin}/mockServiceWorker.js`,
16+
},
17+
});
18+
console.log("MSW started successfully - Using mock API");
19+
} catch (error) {
20+
console.error("Failed to start MSW:", error);
21+
console.log("Falling back to real API server");
1722
}
1823
};

src/pages/Classroom/Classroom.tsx

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -116,38 +116,45 @@ const Classroom = () => {
116116
};
117117

118118
const useSSE = (url: string) => {
119-
const [data, setData] = useState(null);
119+
const [data, setData] = useState<unknown>(null);
120120
const queryClient = useQueryClient();
121121

122122
const subscribe = useCallback(() => {
123123
const eventSource = new EventSource(url, { withCredentials: true });
124124

125125
eventSource.onmessage = (event) => {
126-
const newData = JSON.parse(event.data);
127-
setData(newData);
128-
// setQueryData로 값 캐싱
129-
queryClient.setQueryData(["sse-data"], newData);
130-
guestDataRefetch();
131-
classroomDataRefetch();
126+
try {
127+
const newData = JSON.parse(event.data);
128+
setData(newData);
129+
queryClient.setQueryData(["sse-data"], newData);
130+
guestDataRefetch();
131+
classroomDataRefetch();
132+
} catch (error) {
133+
console.error("Failed to parse SSE message:", error);
134+
}
132135
};
136+
133137
eventSource.addEventListener("action", (event) => {
134-
const newData = JSON.parse(event.data);
135-
setData(newData);
136-
// setQueryData로 값 캐싱
137-
queryClient.setQueryData(["sse-data", classroomId], newData);
138+
try {
139+
const newData = JSON.parse(event.data);
140+
setData(newData);
141+
queryClient.setQueryData(["sse-data", classroomId], newData);
142+
guestDataRefetch();
143+
classroomDataRefetch();
144+
} catch (error) {
145+
console.error("Failed to parse SSE action event:", error);
146+
}
147+
});
138148

139-
guestDataRefetch();
140-
classroomDataRefetch();
149+
eventSource.addEventListener("error", (error) => {
150+
console.error("SSE connection error:", error);
141151
});
142-
// connection되면
143-
eventSource.addEventListener("open", function () {});
144152

145153
return () => {
146154
eventSource.close();
147155
};
148-
}, [url, queryClient]);
156+
}, [url, queryClient, classroomId, guestDataRefetch, classroomDataRefetch]);
149157

150-
// 요청을 끊을 때 일어나는 useEffect
151158
useEffect(() => {
152159
const unsubscribe = subscribe();
153160
return unsubscribe;

src/pages/Visualization/Visualization.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { useEditorStore } from "@/store/editor";
2020
import { useGptTooltipStore } from "@/store/gptTooltip";
2121

2222
export default function Visualization() {
23-
const [code, setCode] = useState<any>(
23+
const [code, setCode] = useState<string>(
2424
[
2525
"for i in range(2, 10):\n" +
2626
" for j in range(1, 10):\n" +
@@ -99,6 +99,14 @@ export default function Visualization() {
9999
setOnboardingStep(newOnboardingStep);
100100
}
101101
};
102+
103+
const closeTutorial = () => {
104+
setCookie("firstVisit", "true", {
105+
path: "/",
106+
maxAge: 7 * 24 * 60 * 60, // 7일(초 단위)
107+
});
108+
setIsTutorialVisible(false);
109+
};
102110
// zustand store
103111
const { resetInputData } = useConsoleStore();
104112
useEffect(() => {
@@ -154,6 +162,9 @@ export default function Visualization() {
154162
left: `${tutorialPosition.left}px`,
155163
}}
156164
>
165+
<button className="tutorial-close-button" onClick={closeTutorial}>
166+
×
167+
</button>
157168
<div className="tutorial-content">
158169
<h2>{steps[currentStep].title}</h2>
159170
<p>{steps[currentStep].description}</p>

src/pages/Visualization/VisualizationClassroom.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ interface ClassAccessRightDataType {
3535
// 원본 코드 타입 정의
3636

3737
const VisualizationClassroom = () => {
38-
const [code, setCode] = useState<any>(
38+
const [code, setCode] = useState<string>(
3939
[
4040
"for i in range(2, 10):\n" +
4141
" for j in range(1, 10):\n" +
@@ -121,6 +121,14 @@ const VisualizationClassroom = () => {
121121
}
122122
};
123123

124+
const closeTutorial = () => {
125+
setCookie("firstVisit", "true", {
126+
path: "/",
127+
maxAge: 7 * 24 * 60 * 60, // 7일(초 단위)
128+
});
129+
setIsTutorialVisible(false);
130+
};
131+
124132
// zustand store
125133
const { focus } = useEditorStore();
126134
const isGptToggle = useGptTooltipStore((state) => state.isGptToggle);
@@ -210,6 +218,9 @@ const VisualizationClassroom = () => {
210218
left: `${tutorialPosition.left}px`,
211219
}}
212220
>
221+
<button className="tutorial-close-button" onClick={closeTutorial}>
222+
×
223+
</button>
213224
<div className="tutorial-content">
214225
<h2>{steps[currentStep].title}</h2>
215226
<p>{steps[currentStep].description}</p>

0 commit comments

Comments
 (0)