Skip to content

Conversation

@hannah0352
Copy link
Collaborator

@hannah0352 hannah0352 commented Oct 2, 2025

๐Ÿ“Œ ์ž‘์—… ๋‚ด์šฉ

  • ์•Œ๋ฆผ ์„ค์ •์— Interval ๊ณ„์‚ฐ ์˜ต์…˜ ์ถ”๊ฐ€

๐Ÿ“ธ ์Šคํฌ๋ฆฐ์ƒท

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2025-10-02 แ„‹แ…ฉแ„’แ…ฎ 10 48 15

๐Ÿ“ ๊ธฐํƒ€

Summary by CodeRabbit

  • New Features
    • ๊ฐ„๊ฒฉ ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ ๋ชจ๋“œ(Interval ๊ณ„์‚ฐ) ์ถ”๊ฐ€: ์ตœ์  ์ƒˆ๋กœ๊ณ ์นจ ์‹œ์  ์•ˆ๋‚ด, ์นด์šดํŠธ๋‹ค์šด ์ž๋™ ์ „ํ™˜, ์‚ฌ์šด๋“œ ์•Œ๋ฆผ ์ง€์›.
    • ๋กœ๋”ฉ ์ƒํƒœ(โ€œ๋„คํŠธ์›Œํฌ ๋ถ„์„ ์ค‘...โ€), ๋ชจ๋“œ๋ณ„ ๋ฉ”์‹œ์ง€, ์ „์ฒด ํ™”๋ฉด ๊ฒฝ๊ณ  ๋ฐฐ๊ฒฝ ํšจ๊ณผ ์ถ”๊ฐ€.
    • ์•Œ๋ฆผ ์„ค์ •์— ๊ณ ๊ธ‰ ์„ค์ •(Interval ํ† ๊ธ€)๊ณผ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์ถ”๊ฐ€; ํ™œ์„ฑ ์‹œ ์‚ฌ์ „ ์•Œ๋ฆผ ์˜์—ญ ๋น„ํ™œ์„ฑํ™”.
  • UI Changes
    • ์ƒ์„ธ ์ •๋ณด ๋ฒ„ํŠผ ์œ„์น˜ ์กฐ์ •.
    • ๋ชฉํ‘œ URL ์ „๋‹ฌ์„ ํ†ตํ•ด ๋ชฉํ‘œ ์‹œ๊ฐ„/์•Œ๋ฆผ ๊ณ„์‚ฐ๊ณผ ์—ฐ๋™ ๊ฐ•ํ™”.

@hannah0352 hannah0352 self-assigned this Oct 2, 2025
@hannah0352 hannah0352 added the feat๐Ÿ› ๏ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ label Oct 2, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 2, 2025

Walkthrough

์•Œ๋ฆผ ์นด์šดํŠธ๋‹ค์šด์— ์ธํ„ฐ๋ฒŒ ๊ธฐ๋ฐ˜ ๊ณ„์‚ฐ ๊ธฐ๋Šฅ๊ณผ ์‚ฌ์šด๋“œ/์ „์ฒดํ™”๋ฉด ๊ฒฝ๊ณ ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , ๋ชจ๋‹ฌ๊ณผ ๊ฒฐ๊ณผ ํ™”๋ฉด์—์„œ finalUrl ์ „๋‹ฌ์„ ํ†ตํ•ด ์ƒˆ ์˜ต์…˜(useIntervalCalculation ๋“ฑ)์„ ์—ฐ๋™ํ–ˆ์Šต๋‹ˆ๋‹ค. ์นด์šดํŠธ๋‹ค์šด์€ /api/interval/calculate์— POST๋กœ ๊ณ„์‚ฐ์„ ์š”์ฒญํ•˜๊ณ  ๊ฒฐ๊ณผ์— ๋”ฐ๋ผ ๋ฉ”์‹œ์ง€/ํ‘œ์‹œ ๋กœ์ง์„ ๋ถ„๊ธฐํ•ฉ๋‹ˆ๋‹ค.

Changes

Cohort / File(s) Summary
Countdown ์ธํ„ฐ๋ฒŒ ๊ณ„์‚ฐ/์•Œ๋ฆผ ํ™•์žฅ
src/components/search-result/AlarmCountdown.tsx
AlarmCountdownProps์— finalUrl?: string ์ถ”๊ฐ€. ์ธํ„ฐ๋ฒŒ ๊ณ„์‚ฐ ๋น„๋™๊ธฐ ํ”Œ๋กœ์šฐ(calculateInterval โ†’ /api/interval/calculate) ๋„์ž…, ์ƒํƒœ ํ™•์žฅ(๊ณ„์‚ฐ/ํ‘œ์‹œ/์‚ฌ์šด๋“œ/๋ฉ”์‹œ์ง€ ์ œ์–ด). ์ธํ„ฐ๋ฒŒ ๋ชจ๋“œ์™€ ๊ธฐ๋ณธ ์‚ฌ์ „ ์•Œ๋ฆผ ๋ชจ๋“œ ๋ถ„๊ธฐ(scheduleIntervalAlerts/scheduleDefaultAlerts). ์ตœ์  ๊ฐฑ์‹  ์‹œ์ ์— ์นด์šดํŠธ๋‹ค์šด ์ˆจ๊น€ ๋ฐ โ€œ์ง€๊ธˆ ์ƒˆ๋กœ๊ณ ์นจํ•˜์„ธ์š”!โ€ ๋ฉ”์‹œ์ง€. Web Audio API ๊ธฐ๋ฐ˜ ์‚ฌ์šด๋“œ, ์•Œ๋žŒ ์‹œ ์ „์ฒด ํ™”๋ฉด ๋นจ๊ฐ„ ๋ฐฐ๊ฒฝ ์ฒ˜๋ฆฌ. ๋ Œ๋”๋ง์— ๋กœ๋”ฉ/๋ชจ๋“œ๋ณ„ ์ •๋ณด/๋ฉ”์‹œ์ง€ ์ถ”๊ฐ€.
๋ชจ๋‹ฌ ์˜ต์…˜/๊ฒ€์ฆ ๋ฐ UI ๊ฐฑ์‹ 
src/components/search-result/AlarmModal.tsx
AlarmModalProps์— finalUrl?: string. AlarmOptions์— useIntervalCalculation: boolean, targetUrl: string, customAlertOffsets: number[] ์ถ”๊ฐ€. ํ† ๊ธ€/์ดˆ๊ธฐ๊ฐ’ ์ฒ˜๋ฆฌ, ์ธํ„ฐ๋ฒŒ ์‚ฌ์šฉ ์‹œ finalUrl ํ•„์ˆ˜ ๊ฒ€์ฆ(๋ฏธ์กด์žฌ ์‹œ alert ๋ฐ ์ œ์ถœ ์ค‘๋‹จ). ๋ผ๋ฒจ/์„น์…˜ ์—…๋ฐ์ดํŠธ ๋ฐ ์‚ฌ์ „ ์•Œ๋ฆผ ์˜์—ญ ๋น„ํ™œ์„ฑํ™” ์ฒ˜๋ฆฌ.
์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ ์—ฐ๋™ ๋ฐ ๋ฐฐ์น˜ ์กฐ์ •
src/components/search-result/ServerTimeResult.tsx
AlarmModal๊ณผ AlarmCountdown์— finalUrl๋กœ data.url ์ „๋‹ฌ. ์ƒ์„ธ ์ •๋ณด ๋ฒ„ํŠผ ์œ„์น˜ ์กฐ์ •(์•Œ๋ฆผ ์„น์…˜ ์ดํ›„๋กœ ์ด๋™). ๊ฒฝ๋ฏธํ•œ ํฌ๋งคํŒ… ์ •๋ฆฌ.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as ์‚ฌ์šฉ์ž
  participant STR as ServerTimeResult
  participant Modal as AlarmModal
  participant AC as AlarmCountdown
  participant API as /api/interval/calculate

  User->>STR: ํŽ˜์ด์ง€ ์กฐํšŒ
  STR->>Modal: props(finalUrl, options)
  STR->>AC: props(alarm, finalUrl)

  alt ์ธํ„ฐ๋ฒŒ ๊ณ„์‚ฐ ์‚ฌ์šฉ
    AC->>API: POST finalUrl๋กœ ์ธํ„ฐ๋ฒŒ ๊ณ„์‚ฐ ์š”์ฒญ
    API-->>AC: 200 OK (optimalRefreshTime, interval ๋“ฑ)
    AC->>AC: alertMessages/์ƒํƒœ ๊ตฌ์„ฑ, ์นด์šดํŠธ๋‹ค์šด ํ‘œ์‹œ
    AC-->>User: ์ตœ์  ์‹œ์  ๋„๋‹ฌ ์‹œ "์ง€๊ธˆ ์ƒˆ๋กœ๊ณ ์นจํ•˜์„ธ์š”!" ํ‘œ์‹œ ๋ฐ ์‚ฌ์šด๋“œ/๋ฐฐ๊ฒฝ
  else ๊ธฐ๋ณธ ๋ชจ๋“œ
    AC->>AC: ์‚ฌ์ „ ์•Œ๋ฆผ ์Šค์ผ€์ค„ ๊ตฌ์„ฑ
    AC-->>User: ์•Œ๋ฆผ ์‹œ๊ฐ„ ๋„๋‹ฌ ์‹œ ๋ฉ”์‹œ์ง€/์‚ฌ์šด๋“œ/๋ฐฐ๊ฒฝ
  end

  Note over Modal,AC: Modal์—์„œ ์ธํ„ฐ๋ฒŒ ํ† ๊ธ€ ์‹œ finalUrl ๊ฒ€์ฆ ์ˆ˜ํ–‰
Loading

Estimated code review effort

๐ŸŽฏ 4 (Complex) | โฑ๏ธ ~60 minutes

Poem

๋‹น๊ทผ ๋“ค๊ณ  ๊นก์ถฉ, ํƒ€์ด๋จธ๋ฅผ ๋ณธ๋‹ค
๋นจ๊ฐ„ ํ•˜๋Š˜ ๋ฒˆ์ฉ, ์‹œ๊ฐ„์€ ์˜จ๋‹ค
๋”ฑ- ๋งž์ถ˜ ์ธํ„ฐ๋ฒŒ, ์ƒˆ๋กœ๊ณ ์นจ ์‹ ํ˜ธ
์‚๋น…โ™ช ์†Œ๋ฆฌ ํƒ€๊ณ , ์•Œ๋ฆผ์ด ํ”ผ์–ด
์˜ค๋Š˜๋„ ์ฝ”๋“œ๋ฐญ, ํ† ๋ผ๋Š” ์ง€์ผœ๋ณธ๋‹ค ๐Ÿฅ•โฑ๏ธ

Pre-merge checks and finishing touches

โŒ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage โš ๏ธ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check โ“ Inconclusive ์ œ๋ชฉ์ด ๋ณ€๊ฒฝ๋œ ๊ธฐ๋Šฅ์„ ๋ช…ํ™•ํžˆ ์„ค๋ช…ํ•˜์ง€ ์•Š๊ณ  ๋ธŒ๋žœ์น˜๋ช…๊ณผ ์ˆซ์ž๋งŒ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด ๋ฌด์—‡์ด ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. PR ์ œ๋ชฉ์„ ํ•ต์‹ฌ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ธ โ€œ์•Œ๋ฆผ ์„ค์ •์— Interval ๊ณ„์‚ฐ ์˜ต์…˜ ์ถ”๊ฐ€โ€ ๋“ฑ์œผ๋กœ ๊ฐ„๊ฒฐํ•˜๊ณ  ๊ตฌ์ฒด์ ์œผ๋กœ ์ˆ˜์ •ํ•ด ์ฃผ์„ธ์š”.
โœ… Passed checks (1 passed)
Check name Status Explanation
Description Check โœ… Passed PR ์„ค๋ช…์€ ํ…œํ”Œ๋ฆฟ์— ๋”ฐ๋ผ ์ž‘์—… ๋‚ด์šฉ๊ณผ ์Šคํฌ๋ฆฐ์ƒท์ด ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ ์ž‘์—… ๋‚ด์šฉ๋„ ๋ช…ํ™•ํžˆ ๊ธฐ์ˆ ๋˜์–ด ์žˆ์–ด ์ „์ฒด์ ์ธ ์š”๊ตฌ ์‚ฌํ•ญ์„ ์ถฉ์กฑํ•ฉ๋‹ˆ๋‹ค.
โœจ Finishing touches
  • ๐Ÿ“ Generate Docstrings
๐Ÿงช Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/search-result3

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

โค๏ธ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

๐Ÿงน Nitpick comments (5)
src/components/search-result/AlarmCountdown.tsx (3)

131-149: optimalRefreshTime ์ฒดํฌ ์œˆ๋„์šฐ๊ฐ€ ๋„ˆ๋ฌด ์ข์Œ

[-1, 0]s ์ฐฝ์€ ํƒญ ๋น„ํ™œ์„ฑ/์Šค์ผ€์ค„ ์ง€์—ฐ ์‹œ ๋†“์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 1) ์ž„๊ณ„๊ฐ’ ๊ต์ฐจ ๊ฐ์ง€(์ด์ „>0, ํ˜„์žฌ<=0) ๋˜๋Š” 2) ์—ฌ์œ  ์œˆ๋„์šฐ ํ™•์žฅ(์˜ˆ: >= -2)์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

๊ฐ„๋‹จ ๋Œ€์•ˆ:

-      if (timeUntilOptimal <= 0 && timeUntilOptimal >= -1) {
+      if (timeUntilOptimal <= 0) {
         // ํ‘œ์‹œ ๋ฐ ์†Œ๋ฆฌ ์žฌ์ƒ์€ ๊ธฐ์กด ๋กœ์ง ์œ ์ง€

๋˜๋Š” ์ด์ „ ๊ฐ’ useRef๋กœ ์œ ์ง€ํ•ด crossing์„ ๊ฐ์ง€ํ•˜์„ธ์š”. ์›ํ•˜์‹œ๋ฉด ์ฝ”๋“œ ์ œ๊ณตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.


252-258: Interval ๋ชจ๋“œ ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง์ด ๋น„์–ด ์žˆ์Œ

intervalResult.data.alertSettings๋ฅผ ์‚ฌ์šฉํ•ด ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€๋ฅผ ์ฑ„์šฐ๋ฉด ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

-  const scheduleIntervalAlerts = () => {
-    // ์•Œ๋ฆผ์„ ์ฆ‰์‹œ ํ‘œ์‹œํ•˜์ง€ ์•Š๊ณ , ๋นˆ ๋ฐฐ์—ด๋กœ ์ดˆ๊ธฐํ™”
-    setAlertMessages([]);
-    console.log('๐ŸŽฏ Interval ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง ์ค€๋น„ ์™„๋ฃŒ');
-  };
+  const scheduleIntervalAlerts = () => {
+    if (!intervalResult?.data?.alertSettings) {
+      setAlertMessages([]);
+      return;
+    }
+    const msgs = intervalResult.data.alertSettings.map((s) =>
+      s.message || `${s.time}์ดˆ ์ „ ์•Œ๋ฆผ`
+    );
+    setAlertMessages(msgs);
+    console.log('๐ŸŽฏ Interval ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง:', msgs);
+  };

49-67: ์ „์—ญ body ๋ฐฐ๊ฒฝ ๋ณ€๊ฒฝ์€ ์ „ํŒŒ ์˜ํ–ฅ ํผ (์„ ํƒ์‚ฌํ•ญ)

ํŽ˜์ด์ง€ ์ „์ฒด์— ๋ถ€์ž‘์šฉ์ด ํผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณ ์ • ์˜ค๋ฒ„๋ ˆ์ด(div, fixed inset-0 bg-red-500 opacity-โ€ฆ])๋กœ ๋Œ€์ฒดํ•˜๋ฉด ๊ฒฉ๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

์›ํ•˜์‹œ๋ฉด ์˜ค๋ฒ„๋ ˆ์ด ๊ตฌํ˜„ ์˜ˆ์‹œ ๋“œ๋ฆด๊ฒŒ์š”.

src/components/search-result/ServerTimeResult.tsx (1)

326-335: ์ƒ์„ธ ์ •๋ณด ๋ฒ„ํŠผ ์ ‘๊ทผ์„ฑ ๊ฐ•ํ™” ์ œ์•ˆ

์Šคํฌ๋ฆฐ ๋ฆฌ๋” ๋ช…ํ™•์„ฑ์„ ์œ„ํ•ด aria-label์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

-        <button
+        <button
+          aria-label="์ƒ์„ธ ์ •๋ณด ์—ด๊ธฐ"
           onClick={() => setShowDetailModal(true)}
           className="inline-flex items-center gap-1.5 px-3 py-1.5 text-gray-500 hover:text-gray-700 transition-colors duration-150 text-xs font-medium"
         >
src/components/search-result/AlarmModal.tsx (1)

16-19: ์˜ต์…˜ ํ•„๋“œ ์ผ๊ด€์„ฑ: targetUrl์ด ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ

ํ˜„์žฌ options.targetUrl์€ ํ•ญ์ƒ ''๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค. ์ œ์ถœ ์‹œ finalUrl์„ ๋ฐ˜์˜ํ•ด ์ผ๊ด€์„ฑ ์œ ์ง€๊ฐ€ ์ข‹์Šต๋‹ˆ๋‹ค(ํ˜น์€ ํ•„๋“œ ์ œ๊ฑฐ).

-  const [options, setOptions] = useState<AlarmOptions>({
+  const [options, setOptions] = useState<AlarmOptions>({
     preAlerts: [],
     sound: false,
     red: false,
     useIntervalCalculation: false,
-    targetUrl: '',
+    targetUrl: '',
     customAlertOffsets: [],
   });

handleSubmit์—์„œ finalUrl์„ ์ฃผ์ž…:

-    onConfirm({
-      time: { hour, minute, second },
-      options,
-      targetTime: targetTime.toISOString(),
-    });
+    onConfirm({
+      time: { hour, minute, second },
+      options: options.useIntervalCalculation && finalUrl
+        ? { ...options, targetUrl: finalUrl }
+        : options,
+      targetTime: targetTime.toISOString(),
+    });

Also applies to: 103-110

๐Ÿ“œ Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

๐Ÿ“ฅ Commits

Reviewing files that changed from the base of the PR and between 3e27d4a and 760cae8.

๐Ÿ“’ Files selected for processing (3)
  • src/components/search-result/AlarmCountdown.tsx (2 hunks)
  • src/components/search-result/AlarmModal.tsx (8 hunks)
  • src/components/search-result/ServerTimeResult.tsx (3 hunks)
๐Ÿงฐ Additional context used
๐Ÿงฌ Code graph analysis (2)
src/components/search-result/AlarmCountdown.tsx (1)
src/components/search-result/AlarmModal.tsx (1)
  • AlarmData (21-25)
src/components/search-result/ServerTimeResult.tsx (2)
src/components/search-result/AlarmModal.tsx (1)
  • AlarmModal (98-307)
src/components/search-result/AlarmCountdown.tsx (1)
  • AlarmCountdown (34-340)
๐Ÿ”‡ Additional comments (4)
src/components/search-result/ServerTimeResult.tsx (2)

285-289: finalUrl ์ „๋‹ฌ ์ถ”๊ฐ€ LGTM

AlarmModal์— finalUrl={data.url} ์ „๋‹ฌ์ด ์ธํ„ฐ๋ฒŒ ๊ณ„์‚ฐ ์˜์กด์„ฑ์— ๋ถ€ํ•ฉํ•ฉ๋‹ˆ๋‹ค.


321-321: finalUrl ์ „๋‹ฌ LGTM

AlarmCountdown์— finalUrl={data.url} ์ „๋‹ฌ๋กœ ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ผ๊ด€์„ฑ ํ™•๋ณด.

src/components/search-result/AlarmModal.tsx (2)

143-148: Interval ์‚ฌ์šฉ ์‹œ finalUrl ๊ฒ€์ฆ LGTM

์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋กœ ์ž˜ ๋ฐฉ์–ดํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

Interval ์‚ฌ์šฉ ์‹œ UI์—์„œ๋„ ๊ฐ„๋‹จ ์•ˆ๋‚ด๋ฌธ๊ตฌ(โ€œ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ URL ํ•„์š”โ€)๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ์‚ฌ์šฉ์„ฑ์ด ๋” ์ข‹์•„์ง‘๋‹ˆ๋‹ค. ํ•„์š”ํ•˜์‹œ๋ฉด ๋ฌธ๊ตฌ/์œ„์น˜ ์ œ์•ˆ ๋“œ๋ฆด๊ฒŒ์š”.


224-226: UI ์ƒํ˜ธ์ž‘์šฉ ์„ค๊ณ„ ์ ์ ˆํ•จ

  • Interval ํ™œ์„ฑํ™” ์‹œ ์‚ฌ์ „ ์•Œ๋ฆผ ์„น์…˜ ๋น„ํ™œ์„ฑํ™” ์ฒ˜๋ฆฌ ์ ์ ˆ.
  • ๊ณ ๊ธ‰ ์„ค์ • ํ† ๊ธ€ ์ถ”๊ฐ€๋„ ๊น”๋”.

Also applies to: 271-287

Comment on lines +68 to +90
// ์†Œ๋ฆฌ ์žฌ์ƒ ํ•จ์ˆ˜ (5์ดˆ๊ฐ„ ์‚ก ์†Œ๋ฆฌ)
const playAlarmSound = () => {
if (!alarm.options.sound) return;

// AudioContext๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ก ์†Œ๋ฆฌ ์ƒ์„ฑ
const AudioContextClass = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
const audioContext = new AudioContextClass();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();

oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);

oscillator.frequency.setValueAtTime(800, audioContext.currentTime); // 800Hz ์‚ก ์†Œ๋ฆฌ
oscillator.type = 'sine';

gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 5); // 5์ดˆ๊ฐ„ ๊ฐ์†Œ

oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 5);
};

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸ  Major

Web Audio ์ปจํ…์ŠคํŠธ ์ •๋ฆฌ ๋ˆ„๋ฝ/์žฌ์ƒ ์ •์ฑ… ๋Œ€์‘ ๋ถ€์กฑ

AudioContext๋ฅผ ๋งค๋ฒˆ ์ƒ์„ฑ ํ›„ ๋‹ซ์ง€ ์•Š์•„ ๋ˆ„์ˆ˜ ์œ„ํ—˜. ๋˜ํ•œ iOS/Safari๋Š” ์‚ฌ์šฉ์ž ์ œ์Šค์ฒ˜ ํ›„ resume ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. onended์—์„œ closeํ•˜๊ณ , ํ•„์š” ์‹œ resume ํ•˜์„ธ์š”.

-  const playAlarmSound = () => {
+  const playAlarmSound = async () => {
     if (!alarm.options.sound) return;
     
     // AudioContext๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‚ก ์†Œ๋ฆฌ ์ƒ์„ฑ
-    const AudioContextClass = window.AudioContext || (window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;
-    const audioContext = new AudioContextClass();
+    const AudioContextClass =
+      (window as any).AudioContext || (window as any).webkitAudioContext;
+    const audioContext = new AudioContextClass();
+    if (audioContext.state === 'suspended') {
+      try { await audioContext.resume(); } catch {}
+    }
     const oscillator = audioContext.createOscillator();
     const gainNode = audioContext.createGain();
@@
-    oscillator.start(audioContext.currentTime);
-    oscillator.stop(audioContext.currentTime + 5);
+    oscillator.onended = () => {
+      audioContext.close().catch(() => {});
+    };
+    oscillator.start();
+    oscillator.stop(audioContext.currentTime + 5);
   };

useEffect์—์„œ Promise๋Š” ๋Œ€๊ธฐํ•˜์ง€ ์•Š์•„๋„ ๋ฉ๋‹ˆ๋‹ค.

Also applies to: 91-99

Comment on lines +100 to +129
// Interval ๊ณ„์‚ฐ API ํ˜ธ์ถœ
const calculateInterval = async (targetUrl: string, targetTime: string, userAlertOffsets: number[]) => {
try {
setIsCalculating(true);
const response = await fetch('http://localhost:3001/api/interval/calculate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
targetUrl,
targetTime,
userAlertOffsets: userAlertOffsets.length > 0 ? userAlertOffsets : undefined,
}),
});

const result = await response.json();
if (result.success) {
setIntervalResult(result);
return result;
} else {
throw new Error(result.error || 'Interval ๊ณ„์‚ฐ ์‹คํŒจ');
}
} catch (error) {
console.error('Interval ๊ณ„์‚ฐ ์˜ค๋ฅ˜:', error);
return null;
} finally {
setIsCalculating(false);
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸ  Major

API ํ˜ธ์ถœ: ํ•˜๋“œ์ฝ”๋”ฉ๋œ localhost, ์‘๋‹ต ์ฝ”๋“œ ๋ฏธ๊ฒ€์ฆ, ํƒ€์ž„์•„์›ƒ ์—†์Œ

ํ”„๋กœ๋•์…˜์—์„œ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค. ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ธฐ๋ฐ˜ ๋ฒ ์ด์Šค URL, response.ok ๊ฒ€์‚ฌ, ์š”์ฒญ ํƒ€์ž„์•„์›ƒ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”.

์˜ˆ์‹œ ์ˆ˜์ •:

-      const response = await fetch('http://localhost:3001/api/interval/calculate', {
+      const base = process.env.NEXT_PUBLIC_API_BASE_URL ?? '';
+      const controller = new AbortController();
+      const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout
+      const response = await fetch(`${base}/api/interval/calculate`, {
         method: 'POST',
         headers: {
           'Content-Type': 'application/json',
         },
         body: JSON.stringify({
           targetUrl,
           targetTime,
           userAlertOffsets: userAlertOffsets.length > 0 ? userAlertOffsets : undefined,
-        }),
+        }),
+        signal: controller.signal,
       });
-
-      const result = await response.json();
+      if (!response.ok) {
+        throw new Error(`HTTP ${response.status}`);
+      }
+      const result = await response.json();
@@
-    } finally {
-      setIsCalculating(false);
-    }
+    } finally {
+      setIsCalculating(false);
+      if (typeof timeoutId !== 'undefined') clearTimeout(timeoutId);
+    }

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ธํŒ…(NEXT_PUBLIC_API_BASE_URL)๋„ ํ•จ๊ป˜ ๋ฐ˜์˜ํ•ด ์ฃผ์„ธ์š”.

Comment on lines 161 to +251
useEffect(() => {
const now = new Date();
const target = new Date();
const initializeCountdown = async () => {
const now = new Date();
const target = new Date();

target.setHours(parseInt(alarm.time.hour));
target.setMinutes(parseInt(alarm.time.minute));
target.setSeconds(parseInt(alarm.time.second));
target.setMilliseconds(0);
target.setHours(parseInt(alarm.time.hour));
target.setMinutes(parseInt(alarm.time.minute));
target.setSeconds(parseInt(alarm.time.second));
target.setMilliseconds(0);

let seconds = Math.floor((target.getTime() - now.getTime()) / 1000);
if (seconds < 0) seconds = 0;
setRemainingSeconds(seconds);

const interval = setInterval(() => {
seconds -= 1;
let seconds = Math.floor((target.getTime() - now.getTime()) / 1000);
if (seconds < 0) seconds = 0;
setRemainingSeconds(seconds);
if (seconds <= 0) {
clearInterval(interval);
setRemainingSeconds(0);
onComplete?.();

// ๋””๋ฒ„๊น…: ์‹œ๊ฐ„ ๊ณ„์‚ฐ ํ™•์ธ
console.log('๐Ÿ• ์‹œ๊ฐ„ ๊ณ„์‚ฐ:', {
now: now.toISOString(),
target: target.toISOString(),
seconds: seconds,
hours: Math.floor(seconds / 3600),
minutes: Math.floor((seconds % 3600) / 60)
});

// Interval ๊ณ„์‚ฐ ์‚ฌ์šฉ ์‹œ API ํ˜ธ์ถœ (ํ•œ ๋ฒˆ๋งŒ)
if (alarm.options.useIntervalCalculation && finalUrl && !hasCalculated) {
setHasCalculated(true);
const result = await calculateInterval(
finalUrl,
target.toISOString(),
alarm.options.customAlertOffsets
);

if (result?.success) {
// ๋””๋ฒ„๊น…: Interval ๊ณ„์‚ฐ ๊ฒฐ๊ณผ ํ™•์ธ
console.log('๐ŸŽฏ Interval ๊ณ„์‚ฐ ๊ฒฐ๊ณผ:', {
optimalRefreshTime: result.data.optimalRefreshTime,
refreshInterval: result.data.refreshInterval,
alertSettings: result.data.alertSettings
});

// Interval ๊ณ„์‚ฐ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง
scheduleIntervalAlerts();
}
} else if (!alarm.options.useIntervalCalculation) {
// ๊ธฐ๋ณธ ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง
scheduleDefaultAlerts(alarm.options.preAlerts);
}
}, 1000);

return () => clearInterval(interval);
}, [alarm, onComplete]);
const interval = setInterval(() => {
seconds -= 1;
setRemainingSeconds(seconds);

// ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ฒดํฌ
checkAlertMessages();

// ๊ธฐ๋ณธ ์•Œ๋ฆผ ๋ชจ๋“œ: ์‚ฌ์ „ ์•Œ๋ฆผ ์‹œ๊ฐ„์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ ์ฒดํฌ
if (!alarm.options.useIntervalCalculation && alarm.options.preAlerts.length > 0) {
alarm.options.preAlerts.forEach((alertSeconds) => {
if (seconds === alertSeconds) {
console.log(`๐Ÿ”” ${alertSeconds}์ดˆ ์ „ ์•Œ๋ฆผ ๋„๋‹ฌ`);
setShowCountdown(false); // ์นด์šดํŠธ๋‹ค์šด ์ˆจ๊น€
setShowAlertTime(true); // ์•Œ๋ฆผ ์‹œ๊ฐ„ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
setRemainingSeconds(0);
// ์†Œ๋ฆฌ๋Š” ์—ฌ๊ธฐ์„œ ์žฌ์ƒํ•˜์ง€ ์•Š์Œ - ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ์‹œ์—๋งŒ ์žฌ์ƒ
// onComplete ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์—ฌ๊ธฐ์„œ ๋ฉˆ์ถค
}
});
}

// ์นด์šดํŠธ๋‹ค์šด์€ ํ•ญ์ƒ ๋ชฉํ‘œ ์‹œ๊ฐ„๊นŒ์ง€ ๊ณ„์† ์ง„ํ–‰
if (seconds <= 0) {
clearInterval(interval);
setRemainingSeconds(0);

// ๊ธฐ๋ณธ ์•Œ๋ฆผ ๋ชจ๋“œ์—์„œ ์‚ฌ์ „ ์•Œ๋ฆผ์ด ์—†์„ ๋•Œ๋„ "์•Œ๋ฆผ ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค!" ํ‘œ์‹œ
if (!alarm.options.useIntervalCalculation) {
setShowCountdown(false);
setShowAlertTime(true);
// ์†Œ๋ฆฌ๋Š” useEffect์—์„œ ์žฌ์ƒ
} else {
onComplete?.();
}
}
}, 1000);

return () => clearInterval(interval);
};

initializeCountdown();
}, [alarm, onComplete, finalUrl, checkAlertMessages, hasCalculated, playAlarmSound]);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ› ๏ธ Refactor suggestion | ๐ŸŸ  Major

useEffect ๋น„๋™๊ธฐ ์ดˆ๊ธฐํ™”๋กœ interval ์ •๋ฆฌ ๋ˆ„๋ฝ๋จ โ€” ์ค‘๋ณต ํƒ€์ด๋จธ/๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์œ„ํ—˜

async ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ๋ฐ˜ํ™˜ํ•œ cleanup์€ useEffect์— ์ „๋‹ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์˜์กด์„ฑ ๋ณ€ํ™” ๋•Œ๋งˆ๋‹ค ํƒ€์ด๋จธ๊ฐ€ ์ค‘์ฒฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์™ธ๋ถ€์—์„œ intervalId๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , useEffect ์ž์ฒด์—์„œ cleanup์„ ๋ฐ˜ํ™˜ํ•˜์„ธ์š”. ๋˜ํ•œ ๋ถˆํ•„์š”ํ•œ ์˜์กด์„ฑ(playAlarmSound, hasCalculated) ์ œ๊ฑฐ๋ฅผ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค.

์ ์šฉ ์˜ˆ์‹œ:

-  useEffect(() => {
-    const initializeCountdown = async () => {
+  useEffect(() => {
+    let intervalId: ReturnType<typeof setInterval> | null = null;
+    const initializeCountdown = async () => {
       const now = new Date();
       const target = new Date();
@@
-      const interval = setInterval(() => {
+      intervalId = setInterval(() => {
         seconds -= 1;
         setRemainingSeconds(seconds);
@@
-      }, 1000);
-
-      return () => clearInterval(interval);
-    };
-
-    initializeCountdown();
-  }, [alarm, onComplete, finalUrl, checkAlertMessages, hasCalculated, playAlarmSound]);
+      }, 1000);
+    };
+    initializeCountdown();
+    return () => {
+      if (intervalId) clearInterval(intervalId);
+    };
+  }, [alarm, onComplete, finalUrl, checkAlertMessages]);

์ถ”๊ฐ€๋กœ, ์ค‘๋ณต API ํ˜ธ์ถœ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด hasCalculated๋Š” useRef๋กœ ๋Œ€์ฒดํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. ํ•„์š” ์‹œ ์ฝ”๋“œ ์ œ๊ณต ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const now = new Date();
const target = new Date();
const initializeCountdown = async () => {
const now = new Date();
const target = new Date();
target.setHours(parseInt(alarm.time.hour));
target.setMinutes(parseInt(alarm.time.minute));
target.setSeconds(parseInt(alarm.time.second));
target.setMilliseconds(0);
target.setHours(parseInt(alarm.time.hour));
target.setMinutes(parseInt(alarm.time.minute));
target.setSeconds(parseInt(alarm.time.second));
target.setMilliseconds(0);
let seconds = Math.floor((target.getTime() - now.getTime()) / 1000);
if (seconds < 0) seconds = 0;
setRemainingSeconds(seconds);
const interval = setInterval(() => {
seconds -= 1;
let seconds = Math.floor((target.getTime() - now.getTime()) / 1000);
if (seconds < 0) seconds = 0;
setRemainingSeconds(seconds);
if (seconds <= 0) {
clearInterval(interval);
setRemainingSeconds(0);
onComplete?.();
// ๋””๋ฒ„๊น…: ์‹œ๊ฐ„ ๊ณ„์‚ฐ ํ™•์ธ
console.log('๐Ÿ• ์‹œ๊ฐ„ ๊ณ„์‚ฐ:', {
now: now.toISOString(),
target: target.toISOString(),
seconds: seconds,
hours: Math.floor(seconds / 3600),
minutes: Math.floor((seconds % 3600) / 60)
});
// Interval ๊ณ„์‚ฐ ์‚ฌ์šฉ ์‹œ API ํ˜ธ์ถœ (ํ•œ ๋ฒˆ๋งŒ)
if (alarm.options.useIntervalCalculation && finalUrl && !hasCalculated) {
setHasCalculated(true);
const result = await calculateInterval(
finalUrl,
target.toISOString(),
alarm.options.customAlertOffsets
);
if (result?.success) {
// ๋””๋ฒ„๊น…: Interval ๊ณ„์‚ฐ ๊ฒฐ๊ณผ ํ™•์ธ
console.log('๐ŸŽฏ Interval ๊ณ„์‚ฐ ๊ฒฐ๊ณผ:', {
optimalRefreshTime: result.data.optimalRefreshTime,
refreshInterval: result.data.refreshInterval,
alertSettings: result.data.alertSettings
});
// Interval ๊ณ„์‚ฐ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง
scheduleIntervalAlerts();
}
} else if (!alarm.options.useIntervalCalculation) {
// ๊ธฐ๋ณธ ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง
scheduleDefaultAlerts(alarm.options.preAlerts);
}
}, 1000);
return () => clearInterval(interval);
}, [alarm, onComplete]);
const interval = setInterval(() => {
seconds -= 1;
setRemainingSeconds(seconds);
// ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ฒดํฌ
checkAlertMessages();
// ๊ธฐ๋ณธ ์•Œ๋ฆผ ๋ชจ๋“œ: ์‚ฌ์ „ ์•Œ๋ฆผ ์‹œ๊ฐ„์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ ์ฒดํฌ
if (!alarm.options.useIntervalCalculation && alarm.options.preAlerts.length > 0) {
alarm.options.preAlerts.forEach((alertSeconds) => {
if (seconds === alertSeconds) {
console.log(`๐Ÿ”” ${alertSeconds}์ดˆ ์ „ ์•Œ๋ฆผ ๋„๋‹ฌ`);
setShowCountdown(false); // ์นด์šดํŠธ๋‹ค์šด ์ˆจ๊น€
setShowAlertTime(true); // ์•Œ๋ฆผ ์‹œ๊ฐ„ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
setRemainingSeconds(0);
// ์†Œ๋ฆฌ๋Š” ์—ฌ๊ธฐ์„œ ์žฌ์ƒํ•˜์ง€ ์•Š์Œ - ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ์‹œ์—๋งŒ ์žฌ์ƒ
// onComplete ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์—ฌ๊ธฐ์„œ ๋ฉˆ์ถค
}
});
}
// ์นด์šดํŠธ๋‹ค์šด์€ ํ•ญ์ƒ ๋ชฉํ‘œ ์‹œ๊ฐ„๊นŒ์ง€ ๊ณ„์† ์ง„ํ–‰
if (seconds <= 0) {
clearInterval(interval);
setRemainingSeconds(0);
// ๊ธฐ๋ณธ ์•Œ๋ฆผ ๋ชจ๋“œ์—์„œ ์‚ฌ์ „ ์•Œ๋ฆผ์ด ์—†์„ ๋•Œ๋„ "์•Œ๋ฆผ ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค!" ํ‘œ์‹œ
if (!alarm.options.useIntervalCalculation) {
setShowCountdown(false);
setShowAlertTime(true);
// ์†Œ๋ฆฌ๋Š” useEffect์—์„œ ์žฌ์ƒ
} else {
onComplete?.();
}
}
}, 1000);
return () => clearInterval(interval);
};
initializeCountdown();
}, [alarm, onComplete, finalUrl, checkAlertMessages, hasCalculated, playAlarmSound]);
useEffect(() => {
let intervalId: ReturnType<typeof setInterval> | null = null;
const initializeCountdown = async () => {
const now = new Date();
const target = new Date();
target.setHours(parseInt(alarm.time.hour));
target.setMinutes(parseInt(alarm.time.minute));
target.setSeconds(parseInt(alarm.time.second));
target.setMilliseconds(0);
let seconds = Math.floor((target.getTime() - now.getTime()) / 1000);
if (seconds < 0) seconds = 0;
setRemainingSeconds(seconds);
// ๋””๋ฒ„๊น…: ์‹œ๊ฐ„ ๊ณ„์‚ฐ ํ™•์ธ
console.log('๐Ÿ• ์‹œ๊ฐ„ ๊ณ„์‚ฐ:', {
now: now.toISOString(),
target: target.toISOString(),
seconds: seconds,
hours: Math.floor(seconds / 3600),
minutes: Math.floor((seconds % 3600) / 60)
});
// Interval ๊ณ„์‚ฐ ์‚ฌ์šฉ ์‹œ API ํ˜ธ์ถœ (ํ•œ ๋ฒˆ๋งŒ)
if (alarm.options.useIntervalCalculation && finalUrl && !hasCalculated) {
setHasCalculated(true);
const result = await calculateInterval(
finalUrl,
target.toISOString(),
alarm.options.customAlertOffsets
);
if (result?.success) {
// ๋””๋ฒ„๊น…: Interval ๊ณ„์‚ฐ ๊ฒฐ๊ณผ ํ™•์ธ
console.log('๐ŸŽฏ Interval ๊ณ„์‚ฐ ๊ฒฐ๊ณผ:', {
optimalRefreshTime: result.data.optimalRefreshTime,
refreshInterval: result.data.refreshInterval,
alertSettings: result.data.alertSettings
});
// Interval ๊ณ„์‚ฐ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง
scheduleIntervalAlerts();
}
} else if (!alarm.options.useIntervalCalculation) {
// ๊ธฐ๋ณธ ์•Œ๋ฆผ ์Šค์ผ€์ค„๋ง
scheduleDefaultAlerts(alarm.options.preAlerts);
}
intervalId = setInterval(() => {
seconds -= 1;
setRemainingSeconds(seconds);
// ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์ฒดํฌ
checkAlertMessages();
// ๊ธฐ๋ณธ ์•Œ๋ฆผ ๋ชจ๋“œ: ์‚ฌ์ „ ์•Œ๋ฆผ ์‹œ๊ฐ„์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ ์ฒดํฌ
if (!alarm.options.useIntervalCalculation && alarm.options.preAlerts.length > 0) {
alarm.options.preAlerts.forEach((alertSeconds) => {
if (seconds === alertSeconds) {
console.log(`๐Ÿ”” ${alertSeconds}์ดˆ ์ „ ์•Œ๋ฆผ ๋„๋‹ฌ`);
setShowCountdown(false); // ์นด์šดํŠธ๋‹ค์šด ์ˆจ๊น€
setShowAlertTime(true); // ์•Œ๋ฆผ ์‹œ๊ฐ„ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
setRemainingSeconds(0);
// ์†Œ๋ฆฌ๋Š” ์—ฌ๊ธฐ์„œ ์žฌ์ƒํ•˜์ง€ ์•Š์Œ - ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ ์‹œ์—๋งŒ ์žฌ์ƒ
// onComplete ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์—ฌ๊ธฐ์„œ ๋ฉˆ์ถค
}
});
}
// ์นด์šดํŠธ๋‹ค์šด์€ ํ•ญ์ƒ ๋ชฉํ‘œ ์‹œ๊ฐ„๊นŒ์ง€ ๊ณ„์† ์ง„ํ–‰
if (seconds <= 0) {
clearInterval(intervalId!);
setRemainingSeconds(0);
// ๊ธฐ๋ณธ ์•Œ๋ฆผ ๋ชจ๋“œ์—์„œ ์‚ฌ์ „ ์•Œ๋ฆผ์ด ์—†์„ ๋•Œ๋„ "์•Œ๋ฆผ ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค!" ํ‘œ์‹œ
if (!alarm.options.useIntervalCalculation) {
setShowCountdown(false);
setShowAlertTime(true);
// ์†Œ๋ฆฌ๋Š” useEffect์—์„œ ์žฌ์ƒ
} else {
onComplete?.();
}
}
}, 1000);
};
initializeCountdown();
return () => {
if (intervalId) clearInterval(intervalId);
};
}, [alarm, onComplete, finalUrl, checkAlertMessages]);
๐Ÿค– Prompt for AI Agents
In src/components/search-result/AlarmCountdown.tsx around lines 161-251, the
useEffect creates an async initializer that sets an interval but returns its
cleanup from inside the async function (so React never receives the cleanup),
causing duplicated timers and potential leaks; refactor so the useEffect itself
is synchronous, declare the intervalId in the outer scope (let intervalId:
ReturnType<typeof setInterval> | null = null), start the async work via an inner
async function but ensure the effect returns a cleanup function that clears
intervalId, use a mounted/aborted flag (or AbortController) to cancel/ignore
async results, replace hasCalculated state with a useRef to prevent re-renders
and duplicate API calls, and remove unnecessary dependencies (playAlarmSound and
hasCalculated) from the dependency array so the interval is cleaned and
recreated correctly on relevant prop changes.

@hannah0352 hannah0352 merged commit 36088fb into main Oct 2, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat๐Ÿ› ๏ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants