Skip to content

Commit 4856e91

Browse files
authored
Merge pull request #16 from WebCreatorX/feature/sidebar
feat: 좌측 사이드바 구현 (main: 컴포넌트 추가 기능)
2 parents f259814 + 9e111be commit 4856e91

Some content is hidden

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

41 files changed

+940
-17
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,6 @@ next-env.d.ts
4646
# 앱별 빌드 결과물 무시
4747
apps/*/out
4848
apps/*/.next
49-
apps/*/dist
49+
apps/*/dist
50+
51+
docs

apps/editor/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313
},
1414
"dependencies": {
1515
"@repo/ui": "workspace:*",
16+
"@repo/utils": "workspace:*",
1617
"@tanstack/react-query": "^5.90.8",
18+
"class-variance-authority": "^0.7.1",
1719
"immer": "^11.1.3",
1820
"json-server": "^1.0.0-beta.3",
21+
"lucide-react": "^0.563.0",
1922
"next": "15.5.9",
2023
"react": "19.1.0",
2124
"react-dom": "19.1.0",

apps/editor/src/app/editor/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import getNodesFromDB from "@/actions/editor/getNodesFromDB";
22
import Canvas from "@/components/editor/Canvas";
33
import EditorStoreInitializer from "@/components/editor/EditorStoreInitializer";
4+
import { LeftSidebar } from "@/widgets/left-sidebar";
45
import { RuntimeProvider } from "@repo/ui/context/runtimeContext";
56

67
export default async function EditorPage() {
@@ -14,7 +15,8 @@ export default async function EditorPage() {
1415
<EditorStoreInitializer initialNodes={nodes}>
1516
{/* 다른 에디터 관련 컴포넌트들은 이곳에서 렌더링 됩니다!(사이드바,매니패스트 수정 컴포넌트 등등...) */}
1617
<RuntimeProvider>
17-
<div className="relative h-full w-full border-2">
18+
<div className="flex h-full w-full border-2">
19+
<LeftSidebar />
1820
<Canvas />
1921
</div>
2022
</RuntimeProvider>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// 타입별 기본값 관리 (캐싱)
2+
// 첫 요청 시 서버에 컴포넌트 기본값 요청
3+
// 두 번째 요청부터는 캐시에서 가져오기
4+
import { useState, useCallback } from 'react';
5+
// 일단 클라이언트에 컴포넌트 기본값이 저장되어있다고 가정. 추후에 서버에서 받을 땐 변경 필요
6+
import { COMPONENT_DEFAULTS, type ComponentDefaults } from '@/shared/lib/component-defaults';
7+
import { WcxNode } from "@repo/ui/types/nodes";
8+
9+
// WcxNode에서 type 필드만 가져오기
10+
type ComponentType = WcxNode["type"];
11+
12+
13+
interface UseComponentDefaultsReturn {
14+
getDefaults: (type: ComponentType) => Promise<ComponentDefaults>;
15+
isLoading: boolean;
16+
cache: Map<ComponentType, ComponentDefaults>;
17+
}
18+
19+
export function useComponentDefaults(): UseComponentDefaultsReturn {
20+
const [cache] = useState(new Map<ComponentType, ComponentDefaults>());
21+
const [isLoading, setIsLoading] = useState(false);
22+
23+
const getDefaults = useCallback(async (type: ComponentType): Promise<ComponentDefaults> => {
24+
// 캐시 확인
25+
if (cache.has(type)) {
26+
return cache.get(type)!;
27+
}
28+
29+
setIsLoading(true);
30+
31+
try {
32+
// 현재는 로컬 상수 사용, 나중에 서버 요청으로 변경 가능
33+
const defaults = COMPONENT_DEFAULTS[type];
34+
35+
// 나중에 서버에서 가져오도록 개선할 때:
36+
// const defaults = await componentApi.fetchDefaults(type);
37+
38+
// 캐시에 저장
39+
cache.set(type, defaults);
40+
41+
return defaults;
42+
} finally {
43+
setIsLoading(false);
44+
}
45+
}, [cache]);
46+
47+
return {
48+
getDefaults,
49+
isLoading,
50+
cache,
51+
};
52+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// DB에 컴포넌트 레코드 생성하도록 서버에 요청하는 훅
2+
// pageId, type, parentId, position 등 컴포넌트 레코드 생성에 필요한 데이터를 전달 후 DB에 저장된 레코드 id, created_at이 추가된 응답값을 받음
3+
// 질문: parentId는 필요한가? 왜냐하면 컴포넌트가 추가될 때는 항상 페이지의 root에 추가되는 것이므로 parentId는 필요하지 않을 것 같음
4+
// 이전에 root에 추가하도록 milo와 얘기나눴음
5+
import { useState } from 'react';
6+
// 일단 componentApi는 mock 응답으로 구현됨
7+
import { componentApi } from '@/shared/api/components';
8+
import type { ComponentRecordRequest, ComponentRecordResponse } from '@/shared/types/component';
9+
10+
interface UseCreateComponentRecordReturn {
11+
createRecord: (data: ComponentRecordRequest) => Promise<ComponentRecordResponse>;
12+
isLoading: boolean;
13+
error: Error | null;
14+
}
15+
16+
export function useCreateComponentRecord(): UseCreateComponentRecordReturn {
17+
const [isLoading, setIsLoading] = useState(false);
18+
const [error, setError] = useState<Error | null>(null);
19+
20+
const createRecord = async (data: ComponentRecordRequest): Promise<ComponentRecordResponse> => {
21+
setIsLoading(true);
22+
setError(null);
23+
24+
try {
25+
const response = await componentApi.createRecord(data);
26+
return response;
27+
} catch (err) {
28+
const error = err instanceof Error ? err : new Error('Unknown error');
29+
setError(error);
30+
throw error;
31+
} finally {
32+
setIsLoading(false);
33+
}
34+
};
35+
36+
return {
37+
createRecord,
38+
isLoading,
39+
error,
40+
};
41+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './model/store';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { create } from 'zustand';
2+
3+
interface Modal {
4+
id: string;
5+
name: string;
6+
}
7+
8+
interface ModalState {
9+
modals: Modal[];
10+
activeModalId: string | null;
11+
selectModal: (id: string) => void;
12+
createModal: () => void;
13+
}
14+
15+
export const useModalStore = create<ModalState>((set) => ({
16+
modals: [
17+
{ id: 'm1', name: '로그인 팝업' },
18+
{ id: 'm2', name: '공지사항' },
19+
],
20+
activeModalId: null,
21+
selectModal: (id: string) => set({ activeModalId: id }),
22+
createModal: () => alert('새 모달 생성 모드 진입'),
23+
}));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './model/store';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { create } from 'zustand';
2+
3+
interface Page {
4+
id: string;
5+
name: string;
6+
}
7+
8+
interface PageState {
9+
pages: Page[];
10+
activePageId: string;
11+
selectPage: (id: string) => void;
12+
}
13+
14+
export const usePageStore = create<PageState>((set) => ({
15+
pages: [
16+
{ id: '1', name: '메인 페이지' },
17+
{ id: '2', name: '상품 상세' },
18+
{ id: '3', name: '이벤트' },
19+
],
20+
activePageId: '1',
21+
selectPage: (id: string) => set({ activePageId: id }),
22+
}));
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './model/store';

0 commit comments

Comments
 (0)