(null);
+
+ useEffect(() => {
+ if (!feedback) return;
+ const h = window.setTimeout(() => setFeedback(null), 2000);
+ return () => window.clearTimeout(h);
+ }, [feedback]);
+
+ const onClick = async () => {
+ if (typeof window === "undefined") return;
+ const url = window.location.href;
+ // Web Share API 우선 — 모바일에서 native sheet 뜸. 데스크탑/실패 시 클립보드로 폴백.
+ const shareData = {
+ title: name ? `${name} (${code})` : code,
+ text: name ? `${name} 종목 정보` : code,
+ url,
+ };
+ if (
+ typeof navigator !== "undefined" &&
+ typeof navigator.share === "function"
+ ) {
+ try {
+ await navigator.share(shareData);
+ return; // 사용자가 native sheet 에서 처리. 토스트 안 띄움.
+ } catch {
+ // AbortError(사용자 취소) 또는 미지원 — 클립보드로 폴백.
+ }
+ }
+ const ok = await copyToClipboard(url);
+ setFeedback(ok ? "링크 복사됨" : "복사 안됨");
+ };
+
+ return (
+
+
+ {feedback && (
+
+ {feedback}
+
+ )}
+
+ );
+}
diff --git a/web/lib/notes.ts b/web/lib/notes.ts
index 5f6b5e6..c2e6b09 100644
--- a/web/lib/notes.ts
+++ b/web/lib/notes.ts
@@ -24,20 +24,25 @@ export function readNote(code: string): Note | null {
}
}
-export function writeNote(code: string, text: string): Note | null {
- if (typeof window === "undefined") return null;
+export type WriteResult =
+ | { status: "saved"; note: Note }
+ | { status: "deleted" }
+ | { status: "failed" };
+
+export function writeNote(code: string, text: string): WriteResult {
+ if (typeof window === "undefined") return { status: "failed" };
const trimmed = text.slice(0, MAX_LEN);
- // 공백만이면 키 자체 제거 → 빈 잔여물 방지.
+ // 공백만이면 키 자체 제거 → 빈 잔여물 방지. 의도된 삭제이므로 failed 와 구분.
if (!trimmed.trim()) {
clearNote(code);
- return null;
+ return { status: "deleted" };
}
const next: Note = { text: trimmed, updatedAt: new Date().toISOString() };
try {
window.localStorage.setItem(`${PREFIX}${code}`, JSON.stringify(next));
- return next;
+ return { status: "saved", note: next };
} catch {
- return null; // quota 등 — UI 가 저장 실패 처리
+ return { status: "failed" }; // quota 등 — UI 가 저장 실패 처리
}
}