All files / components RedirectModal.tsx

100% Statements 15/15
90% Branches 9/10
100% Functions 6/6
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129                                                                  7x   7x 7x 6x   6x 1x 1x       6x 6x 6x         7x   6x                       3x                                                                       1x                                                    
import React, { useEffect, useRef } from "react";
 
interface RedirectModalProps {
  isOpen: boolean;
  onClose: () => void;
  onConfirm: () => void;
  remember: boolean;
  setRemember: (value: boolean) => void;
  showSuccessMessage: boolean;
}
 
/**
 * ユーザー向けリダイレクト確認モーダル。
 *
 * 主に「食事データ登録」フローの開始前に表示され、ユーザーの意思確認と設定の保存を行う。
 * 成功メッセージの表示機能も兼ね備えている。
 *
 * Props:
 * @param isOpen - モーダルの表示状態。
 * @param onClose - モーダルを閉じる際(背景クリック、Escapeキー、閉じるボタン)のコールバック。
 * @param onConfirm - 「登録する」ボタン押下時のコールバック。
 * @param remember - 「次回以降も記憶する」チェックボックスの状態。
 * @param setRemember - チェックボックスの状態更新関数。
 * @param showSuccessMessage - 認証成功メッセージを表示するかどうか。
 */
export default function RedirectModal({
  isOpen,
  onClose,
  onConfirm,
  remember,
  setRemember,
  showSuccessMessage,
}: RedirectModalProps) {
  const modalRef = useRef<HTMLDivElement>(null);
 
  useEffect(() => {
    if (isOpen) {
      modalRef.current?.focus();
 
      const handleKeyDown = (e: KeyboardEvent) => {
        Eif (e.key === "Escape") {
          onClose();
        }
      };
 
      document.addEventListener("keydown", handleKeyDown);
      return () => {
        document.removeEventListener("keydown", handleKeyDown);
      };
    }
  }, [isOpen, onClose]);
 
  if (!isOpen) return null;
 
  return (
    <div
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-xs p-4"
      onClick={onClose}
      role="dialog"
      aria-modal="true"
      aria-labelledby="modal-title"
    >
      <div
        ref={modalRef}
        tabIndex={-1}
        className="bg-gray-800 border border-gray-700 rounded-xl p-6 max-w-sm w-full shadow-2xl transform transition-all outline-none"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex flex-col gap-6">
          {showSuccessMessage && (
            <div className="bg-green-900/30 border border-green-700 rounded-lg p-4 mb-2">
              <h2
                id="modal-title"
                className="text-xl font-bold text-white mb-2"
              >
                登録ありがとうございます
              </h2>
              <p className="text-green-400">
                ✅ Fitbitの認証が確認できました!
              </p>
            </div>
          )}
 
          {!showSuccessMessage && (
            <h2 id="modal-title" className="sr-only">
              食事の記録登録
            </h2>
          )}
 
          <button
            onClick={onConfirm}
            className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-4 px-6 rounded-lg transition-colors w-full shadow-lg"
          >
            食事の記録を登録する
          </button>
 
          <div className="flex flex-col gap-2">
            <div className="flex items-center justify-center gap-3 bg-gray-700/30 p-3 rounded-lg border border-gray-700">
              <input
                type="checkbox"
                id="remember-redirect"
                checked={remember}
                onChange={(e) => setRemember(e.target.checked)}
                className="w-5 h-5 text-blue-500 bg-gray-700 border-gray-500 rounded focus:ring-blue-500 focus:ring-offset-gray-800 cursor-pointer"
              />
              <label
                htmlFor="remember-redirect"
                className="text-sm text-gray-300 cursor-pointer select-none"
              >
                次回以降もこの設定を記憶する
              </label>
            </div>
            <p className="text-xs text-gray-400 text-center px-1">
              チェックをつけることで、次回以降トップページから直接JSON登録ページに遷移します。
            </p>
          </div>
 
          <button
            onClick={onClose}
            className="text-gray-400 hover:text-white text-sm text-center transition-colors"
          >
            閉じる
          </button>
        </div>
      </div>
    </div>
  );
}