Skip to content

Commit 4d4ff20

Browse files
committed
feat: 튜토리얼 관련 UI 개선 및 상태 관리 로직 수정, 서비스 생성 폼 상태 제어 추가
1 parent e53ec78 commit 4d4ff20

8 files changed

Lines changed: 301 additions & 662 deletions

File tree

src/app/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,16 @@ import { ContainerDetailsPage } from '@/pages/containers/container-details';
44
import { DashboardPage } from '@/pages/dashboard';
55
import { ServiceDetailsPage, ServicesPage } from '@/pages/services';
66
import { TutorialPage } from '@/pages/tutorial';
7-
import { TutorialWidget } from '@/widgets/tutorial';
87

8+
import { TutorialWidget } from '@/widgets/tutorial';
99
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
1010
import { Providers } from './providers';
1111

1212
export const App = () => {
1313
return (
1414
<div className="w-full h-full flex flex-col">
1515
<Providers>
16+
<TutorialWidget />
1617
<BrowserRouter basename="/CM2-Frontend/">
1718
<Routes>
1819
<Route path="/" element={<DashboardPage />} />
@@ -25,7 +26,6 @@ export const App = () => {
2526
<Route path="/tutorial" element={<TutorialPage />} />
2627
<Route path="*" element={<Navigate to="/" replace />} />
2728
</Routes>
28-
<TutorialWidget />
2929
</BrowserRouter>
3030
</Providers>
3131
</div>

src/pages/cluster/cluster-page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ export const ClusterPage = () => {
253253
{clusterNodes.nodes.map((node) => (
254254
<div key={node.id} className="p-6 hover:bg-gray-50 transition-colors">
255255
<div className="flex items-center justify-between">
256-
<div className="flex items-center gap-4">
256+
<div className="flex items-center gap-4" data-tour="node-details" tabIndex={0}>
257257
{getRoleIcon(node.managerStatus)}
258258
<div>
259259
<div className="flex items-center gap-3">

src/pages/services/services-page.tsx

Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,26 @@ import { Button } from '@/components/ui/button';
22
import { servicesApi, useServices } from '@/shared/api';
33
import { CreateServiceRequest, Service } from '@/shared/api/services';
44
import { useToastContext } from '@/shared/contexts';
5+
import { useTutorialStore } from '@/shared/stores/tutorial-store';
56
import { Card } from '@/shared/ui/card/card';
67
import { Layout } from '@/widgets/layout';
78
import { useMutation, useQueryClient } from '@tanstack/react-query';
89
import {
9-
AlertTriangle,
10-
Container,
11-
Database,
12-
Globe,
13-
Minus,
14-
Monitor,
15-
Network,
16-
Play,
17-
Plus,
18-
RefreshCw,
19-
Server,
20-
Settings,
21-
Square,
22-
Trash2,
23-
Zap
10+
AlertTriangle,
11+
Container,
12+
Database,
13+
Globe,
14+
Minus,
15+
Monitor,
16+
Network,
17+
Play,
18+
Plus,
19+
RefreshCw,
20+
Server,
21+
Settings,
22+
Square,
23+
Trash2,
24+
Zap
2425
} from 'lucide-react';
2526
import { useState } from 'react';
2627
import { useNavigate } from 'react-router-dom';
@@ -29,7 +30,8 @@ export const ServicesPage = () => {
2930
const navigate = useNavigate();
3031
const queryClient = useQueryClient();
3132
const { showSuccess, showError, showWarning } = useToastContext();
32-
const [showCreateForm, setShowCreateForm] = useState(false);
33+
const serviceCreateFormOpen = useTutorialStore((s) => s.serviceCreateFormOpen);
34+
const setServiceCreateFormOpen = useTutorialStore((s) => s.setServiceCreateFormOpen);
3335
const [newServiceName, setNewServiceName] = useState('');
3436
const [newServiceImage, setNewServiceImage] = useState('nginx:latest');
3537

@@ -46,7 +48,7 @@ export const ServicesPage = () => {
4648
onSuccess: () => {
4749
queryClient.invalidateQueries({ queryKey: ['services'] });
4850
showSuccess('서비스가 성공적으로 생성되었습니다.');
49-
setShowCreateForm(false);
51+
setServiceCreateFormOpen(false);
5052
setNewServiceName('');
5153
setNewServiceImage('nginx:latest');
5254
},
@@ -211,7 +213,7 @@ export const ServicesPage = () => {
211213
새로고침
212214
</Button>
213215
<Button
214-
onClick={() => setShowCreateForm(!showCreateForm)}
216+
onClick={() => setServiceCreateFormOpen(!serviceCreateFormOpen)}
215217
className="flex items-center gap-2"
216218
data-tour="create-service-button"
217219
>
@@ -222,7 +224,7 @@ export const ServicesPage = () => {
222224
</div>
223225

224226
{/* 서비스 생성 폼 */}
225-
{showCreateForm && (
227+
{serviceCreateFormOpen && (
226228
<Card className="p-6" data-tour="service-create-form">
227229
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
228230
<Plus className="w-5 h-5" />
@@ -289,26 +291,20 @@ export const ServicesPage = () => {
289291
</div>
290292
</div>
291293

292-
<div className="flex items-center gap-3 mt-6">
293-
<Button
294-
onClick={handleCreateService}
295-
disabled={createServiceMutation.isPending}
296-
className="flex items-center gap-2"
297-
data-tour="submit-service-button"
298-
>
299-
{createServiceMutation.isPending ? (
300-
<RefreshCw className="w-4 h-4 animate-spin" />
301-
) : (
302-
<Plus className="w-4 h-4" />
303-
)}
304-
생성
305-
</Button>
306-
<Button
307-
onClick={() => setShowCreateForm(false)}
294+
<div className="flex justify-end gap-2 mt-6">
295+
<Button
308296
variant="outline"
297+
onClick={() => setServiceCreateFormOpen(false)}
309298
>
310299
취소
311300
</Button>
301+
<Button
302+
onClick={handleCreateService}
303+
data-tour="submit-service-button"
304+
disabled={createServiceMutation.isPending}
305+
>
306+
{createServiceMutation.isPending ? '생성 중...' : '생성'}
307+
</Button>
312308
</div>
313309
</Card>
314310
)}
@@ -419,6 +415,12 @@ export const ServicesPage = () => {
419415
>
420416
{service.replicas || 1}
421417
</span>
418+
<span
419+
className="sr-only"
420+
data-tour="confirm-scale-up"
421+
>
422+
스케일업 완료 튜토리얼 타겟
423+
</span>
422424
<Button
423425
size="sm"
424426
variant="ghost"
@@ -487,7 +489,7 @@ export const ServicesPage = () => {
487489
첫 번째 서비스를 생성하여 애플리케이션을 배포해보세요.
488490
</p>
489491
<Button
490-
onClick={() => setShowCreateForm(true)}
492+
onClick={() => setServiceCreateFormOpen(true)}
491493
className="flex items-center gap-2"
492494
>
493495
<Plus className="w-4 h-4" />

src/pages/tutorial/tutorial-page.tsx

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,48 @@ import { Layout } from '@/widgets/layout';
44
import { BookOpen, LifeBuoy, Network, Server } from 'lucide-react';
55
import { useEffect, useRef } from 'react';
66

7+
// 시나리오 하드코딩 (카드용, 실제 단계는 store에서 관리)
8+
const scenarios = [
9+
{
10+
id: 'service-lifecycle',
11+
title: '서비스 생명주기 관리',
12+
description: '서비스 생성부터 삭제까지 전체 생명주기를 관리하는 방법을 학습합니다.',
13+
steps: 7,
14+
tourTarget: 'go-services',
15+
},
16+
{
17+
id: 'cluster-monitoring',
18+
title: '클러스터 모니터링',
19+
description: '클러스터 전체 상태와 개별 노드의 상태를 모니터링하는 방법을 학습합니다.',
20+
steps: 6,
21+
tourTarget: 'go-cluster',
22+
},
23+
{
24+
id: 'failure-handling',
25+
title: '장애 대응',
26+
description: '시스템 장애 상황을 시뮬레이션하고 대응하는 방법을 학습합니다.',
27+
steps: 5,
28+
tourTarget: 'go-cluster',
29+
},
30+
{
31+
id: 'scale-up',
32+
title: '서비스 스케일업',
33+
description: '서비스의 레플리카 수를 조정하여 확장하는 방법을 학습합니다.',
34+
steps: 4,
35+
tourTarget: 'go-services',
36+
},
37+
];
38+
739
export const TutorialPage = () => {
8-
const { scenarios, openTutorial, isOpen } = useTutorialStore();
940
const scenarioRefs = useRef<(HTMLDivElement | null)[]>([]);
41+
const openTutorial = useTutorialStore((s) => s.openTutorial);
1042

1143
useEffect(() => {
1244
if (scenarioRefs.current[0]) {
1345
scenarioRefs.current[0].focus();
1446
}
1547
}, []);
1648

17-
const handleStartTutorial = (scenarioId: string) => {
18-
openTutorial(scenarioId);
19-
};
20-
2149
// 시나리오별 대표 아이콘 매핑
2250
const scenarioIcons: Record<string, React.ReactNode> = {
2351
'service-lifecycle': <Server className="w-8 h-8 text-blue-500" />,
@@ -54,7 +82,8 @@ export const TutorialPage = () => {
5482
}}
5583
tabIndex={0}
5684
className="cursor-pointer transition-transform hover:scale-[1.025] focus:outline-none focus:ring-2 focus:ring-blue-500 group"
57-
onClick={() => handleStartTutorial(scenario.id)}
85+
onClick={() => openTutorial(scenario.id)}
86+
data-tour={scenario.tourTarget}
5887
>
5988
<CardContent className="flex flex-col gap-2 min-h-[160px] justify-between">
6089
<div className="flex items-center gap-3 mb-2">
@@ -66,22 +95,15 @@ export const TutorialPage = () => {
6695
</div>
6796
<p className="text-gray-700 dark:text-gray-300 mb-2 flex-1">{scenario.description}</p>
6897
<div className="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400 mt-2">
69-
<span>{scenario.steps.length}단계</span>
98+
<span>{scenario.steps}단계</span>
7099
<span></span>
71-
<span>{Math.ceil(scenario.steps.length * 1.5)}</span>
100+
<span>{Math.ceil(scenario.steps * 1.5)}</span>
72101
</div>
73102
</CardContent>
74103
</Card>
75104
))}
76105
</div>
77106
</section>
78-
79-
{/* 오버레이(투어) 진행 시 전체 오버레이만 표시 */}
80-
{isOpen && (
81-
<div className="fixed inset-0 z-50">
82-
{/* TutorialOverlay는 글로벌하게 렌더링되므로 별도 표시 불필요 */}
83-
</div>
84-
)}
85107
</Layout>
86108
);
87109
};

0 commit comments

Comments
 (0)