Skip to content

Commit 7621ebf

Browse files
authored
Merge pull request #25 from LinkyBoard/dev
[WEB] 배포 최신화
2 parents 3cc494d + d7a3d46 commit 7621ebf

10 files changed

Lines changed: 265 additions & 234 deletions

File tree

apps/web/src/components/landing/hero-section.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default function HeroSection() {
3636
<Button
3737
size="lg"
3838
className="flex items-center gap-2"
39-
onClick={() => router.push("/dashboard")}
39+
onClick={() => router.push("/login")}
4040
aria-label="무료로 시작하기"
4141
>
4242
무료로 시작하기

apps/web/src/components/landing/how-it-works-section.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export default function HowItWorksSection() {
5555
<Button
5656
size="lg"
5757
className="mx-auto flex items-center gap-2"
58-
onClick={() => router.push("/dashboard")}
58+
onClick={() => router.push("/login")}
5959
aria-label="무료로 시작하기"
6060
>
6161
무료로 시작하기
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useEffect, useState } from "react";
2+
3+
import { cn } from "@linkyboard/utils";
4+
5+
interface ResizeBarProps {
6+
className?: string;
7+
onMouseMove?: (e: MouseEvent) => void;
8+
onMouseUp?: (e: MouseEvent) => void;
9+
}
10+
11+
export default function ResizeBar({ className, onMouseMove, onMouseUp }: ResizeBarProps) {
12+
const [isResizing, setIsResizing] = useState(false);
13+
14+
const onMouseDown = (e: React.MouseEvent) => {
15+
e.preventDefault();
16+
setIsResizing(true);
17+
};
18+
19+
const onMouseMoveEvent = (e: MouseEvent) => {
20+
if (!isResizing) return;
21+
22+
onMouseMove?.(e);
23+
};
24+
25+
const onMouseUpEvent = (e: MouseEvent) => {
26+
setIsResizing(false);
27+
onMouseUp?.(e);
28+
};
29+
30+
useEffect(() => {
31+
if (isResizing) {
32+
document.addEventListener("mousemove", onMouseMoveEvent);
33+
document.addEventListener("mouseup", onMouseUpEvent);
34+
document.body.style.cursor = "col-resize";
35+
document.body.style.userSelect = "none";
36+
37+
return () => {
38+
document.removeEventListener("mousemove", onMouseMoveEvent);
39+
document.removeEventListener("mouseup", onMouseUpEvent);
40+
document.body.style.cursor = "default";
41+
document.body.style.userSelect = "auto";
42+
};
43+
}
44+
}, [isResizing]);
45+
46+
return (
47+
<div
48+
className={cn("bg-border relative w-1 cursor-col-resize transition-colors", className)}
49+
onMouseDown={onMouseDown}
50+
aria-label="사이드바 크기 조절"
51+
>
52+
<div className="bg-muted-foreground/50 absolute left-1/2 top-1/2 h-6 w-0.5 -translate-x-1/2 -translate-y-1/2" />
53+
</div>
54+
);
55+
}

apps/web/src/components/sentinel-spinner.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ export default function SentinelSpinner({
4343

4444
return (
4545
<>
46-
{!isLoading && isFetchingNextPage && <Loader2 className={cn("animate-spin", className)} />}
46+
{!isLoading && isFetchingNextPage && (
47+
<Loader2 className={cn("mx-auto animate-spin", className)} />
48+
)}
4749
<div ref={sentinelRef} />
4850
</>
4951
);

apps/web/src/components/sidebar.tsx

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"use client";
22

3-
import { useEffect, useState } from "react";
3+
import { useState } from "react";
44

55
import { useOutsideClick } from "@linkyboard/hooks";
66
import { cn } from "@linkyboard/utils";
77

8+
import ResizeBar from "./resize-bar";
9+
810
interface SidebarProps {
911
isOpen: boolean;
1012
onClose: () => void;
@@ -15,7 +17,6 @@ const MIN_WIDTH = 448;
1517
const MAX_WIDTH = 576;
1618

1719
export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
18-
const [isResizing, setIsResizing] = useState(false);
1920
const [sidebarWidth, setSidebarWidth] = useState(448);
2021

2122
const [sidebarRef] = useOutsideClick<HTMLDivElement>(() => {
@@ -24,42 +25,14 @@ export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
2425
}
2526
});
2627

27-
const onMouseDown = (e: React.MouseEvent) => {
28-
e.preventDefault();
29-
setIsResizing(true);
30-
};
31-
3228
const onMouseMove = (e: MouseEvent) => {
33-
if (!isResizing) return;
34-
3529
const newWidth = window.innerWidth - e.clientX;
3630

3731
if (newWidth >= MIN_WIDTH && newWidth <= MAX_WIDTH) {
3832
setSidebarWidth(newWidth);
3933
}
4034
};
4135

42-
const onMouseUp = () => {
43-
setIsResizing(false);
44-
};
45-
46-
// 마우스 이벤트 리스너 등록
47-
useEffect(() => {
48-
if (isResizing) {
49-
document.addEventListener("mousemove", onMouseMove);
50-
document.addEventListener("mouseup", onMouseUp);
51-
document.body.style.cursor = "col-resize";
52-
document.body.style.userSelect = "none";
53-
54-
return () => {
55-
document.removeEventListener("mousemove", onMouseMove);
56-
document.removeEventListener("mouseup", onMouseUp);
57-
document.body.style.cursor = "default";
58-
document.body.style.userSelect = "auto";
59-
};
60-
}
61-
}, [isResizing]);
62-
6336
return (
6437
<div
6538
className={cn(
@@ -78,14 +51,7 @@ export default function Sidebar({ isOpen, onClose, children }: SidebarProps) {
7851
>
7952
{children}
8053

81-
{/* Resize Handle */}
82-
<div
83-
className="bg-border absolute left-0 top-0 h-full w-1 cursor-col-resize transition-colors"
84-
onMouseDown={onMouseDown}
85-
aria-label="사이드바 크기 조절"
86-
>
87-
<div className="bg-muted-foreground/50 absolute left-1/2 top-1/2 h-6 w-0.5 -translate-x-1/2 -translate-y-1/2" />
88-
</div>
54+
<ResizeBar className="absolute left-0 top-0 h-full" onMouseMove={onMouseMove} />
8955
</div>
9056
</div>
9157
);

apps/web/src/components/topic/content-list.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface ContentListProps {
2121
contentPanelWidth: number;
2222
type: ContentTypeOptions;
2323
id: string;
24+
isTopicLoading: boolean;
2425
nodes: TopicNodeProps[];
2526
}
2627

@@ -29,6 +30,7 @@ export default function ContentList({
2930
contentPanelWidth,
3031
type,
3132
id,
33+
isTopicLoading,
3234
nodes,
3335
}: ContentListProps) {
3436
const [searchQuery, setSearchQuery] = useState("");
@@ -109,7 +111,7 @@ export default function ContentList({
109111
</Button>
110112
</div>
111113
<div className="flex h-[calc(100%-120px)] flex-col gap-3 overflow-y-auto p-4">
112-
{isLoading ? (
114+
{isLoading || isTopicLoading ? (
113115
<div className="flex justify-center">
114116
<Loader2 className="text-muted-foreground animate-spin" />
115117
</div>

apps/web/src/components/topic/content-sticker.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export default function ContentSticker({
100100
{shouldShowMemo && (
101101
<div className="mt-4 flex-1 rounded-lg bg-blue-50 p-3">
102102
<h4 className="mb-2 text-sm font-semibold text-blue-900">메모</h4>
103-
<p className="text-sm leading-relaxed text-blue-800">{item.memo}</p>
103+
<p className="text-sm leading-relaxed text-blue-800">{item.memo || "-"}</p>
104104
</div>
105105
)}
106106
</div>

apps/web/src/components/topic/custom-node.tsx

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect } from "react";
1+
import { useEffect, useRef } from "react";
22

33
import { useDebounce } from "@/hooks/use-debounce";
44
import {
@@ -41,6 +41,9 @@ const stickerStyle = {
4141
export default function CustomNode(props: CustomNodeProps) {
4242
const nodeData = props.data as unknown as NodeData;
4343

44+
const previousPositionRef = useRef<{ posX: number; posY: number } | null>(null);
45+
const previousSizeRef = useRef<{ width: number; height: number } | null>(null);
46+
4447
const connection = useConnection();
4548

4649
// 콘텐츠 위치, 크기 변경 시 호출
@@ -64,11 +67,32 @@ export default function CustomNode(props: CustomNodeProps) {
6467

6568
// 위치 변경 시 updateContentPosition 호출
6669
useEffect(() => {
67-
const body = {
68-
topicId: props.topicId,
70+
const currentPosition = {
6971
posX: debouncedProps.positionAbsoluteX,
7072
posY: debouncedProps.positionAbsoluteY,
7173
};
74+
75+
// 초기 마운트 시에는 이전 위치를 저장하고 API 호출하지 않음
76+
if (previousPositionRef.current === null) {
77+
previousPositionRef.current = currentPosition;
78+
return;
79+
}
80+
81+
// 위치가 실제로 변경되지 않았으면 API 호출하지 않음
82+
if (
83+
previousPositionRef.current.posX === currentPosition.posX &&
84+
previousPositionRef.current.posY === currentPosition.posY
85+
) {
86+
return;
87+
}
88+
89+
previousPositionRef.current = currentPosition;
90+
91+
const body = {
92+
topicId: props.topicId,
93+
...currentPosition,
94+
};
95+
7296
const updatePosition = async () => {
7397
switch (nodeData.nodeContent) {
7498
case "topic":
@@ -90,16 +114,40 @@ export default function CustomNode(props: CustomNodeProps) {
90114
return;
91115
}
92116
};
117+
93118
updatePosition();
119+
120+
// eslint-disable-next-line react-hooks/exhaustive-deps
94121
}, [debouncedProps.positionAbsoluteX, debouncedProps.positionAbsoluteY]);
95122

96123
// 크기 변경 시 updateContentSize 호출
97124
useEffect(() => {
98-
const body = {
99-
topicId: props.topicId,
125+
const currentSize = {
100126
width: debouncedProps.width ?? 350,
101127
height: debouncedProps.height ?? 220,
102128
};
129+
130+
// 초기 마운트 시에는 이전 크기를 저장하고 API 호출하지 않음
131+
if (previousSizeRef.current === null) {
132+
previousSizeRef.current = currentSize;
133+
return;
134+
}
135+
136+
// 크기가 실제로 변경되지 않았으면 API 호출하지 않음
137+
if (
138+
previousSizeRef.current.width === currentSize.width &&
139+
previousSizeRef.current.height === currentSize.height
140+
) {
141+
return;
142+
}
143+
144+
previousSizeRef.current = currentSize;
145+
146+
const body = {
147+
topicId: props.topicId,
148+
...currentSize,
149+
};
150+
103151
const updateSize = async () => {
104152
switch (nodeData.nodeContent) {
105153
case "topic":
@@ -121,7 +169,10 @@ export default function CustomNode(props: CustomNodeProps) {
121169
return;
122170
}
123171
};
172+
124173
updateSize();
174+
175+
// eslint-disable-next-line react-hooks/exhaustive-deps
125176
}, [debouncedProps.width, debouncedProps.height]);
126177

127178
return (

0 commit comments

Comments
 (0)