summaryrefslogtreecommitdiff
path: root/makima/frontend/src/components/mesh/ContractCompleteQuestion.tsx
blob: d4ef618bfb9e53bd4f9407e42b8fbb2a38cb84ce (plain) (blame)
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { useState } from "react";
import type { PendingQuestion } from "../../lib/api";

interface ContractCompleteQuestionProps {
  question: PendingQuestion;
  onAnswer: (questionId: string, response: string) => Promise<void>;
}

/**
 * Component for displaying contract_complete type questions prominently on the task page.
 * These questions persist until answered and are not shown as floating notifications.
 */
export function ContractCompleteQuestion({
  question,
  onAnswer,
}: ContractCompleteQuestionProps) {
  const [submitting, setSubmitting] = useState(false);
  const [minimized, setMinimized] = useState(false);
  const [customInput, setCustomInput] = useState("");
  const [showCustom, setShowCustom] = useState(false);

  const handleAnswer = async (response: string) => {
    if (submitting) return;
    setSubmitting(true);
    try {
      await onAnswer(question.questionId, response);
    } finally {
      setSubmitting(false);
    }
  };

  const handleCustomSubmit = async () => {
    if (!customInput.trim() || submitting) return;
    await handleAnswer(customInput.trim());
    setCustomInput("");
    setShowCustom(false);
  };

  // Default choices for contract completion questions
  const defaultChoices =
    question.choices.length > 0
      ? question.choices
      : ["Yes, contract is complete", "No, more work needed"];

  if (minimized) {
    return (
      <div className="fixed bottom-4 left-4 z-40">
        <button
          onClick={() => setMinimized(false)}
          className="flex items-center gap-2 px-4 py-2 bg-green-600 hover:bg-green-500 text-white font-mono text-sm rounded-lg shadow-lg transition-colors"
        >
          <span className="w-2 h-2 bg-white rounded-full animate-pulse" />
          Contract Review Pending
        </button>
      </div>
    );
  }

  return (
    <div className="bg-gradient-to-r from-green-900/40 to-emerald-900/40 border-2 border-green-500/60 rounded-lg shadow-xl my-4 overflow-hidden">
      {/* Header */}
      <div className="flex items-center justify-between px-4 py-3 bg-green-900/50 border-b border-green-500/30">
        <div className="flex items-center gap-3">
          <div className="w-8 h-8 flex items-center justify-center bg-green-500/20 rounded-full">
            <span className="text-green-400 text-xl">?</span>
          </div>
          <div>
            <h3 className="font-mono text-sm text-green-300 uppercase tracking-wide">
              Contract Completion Review
            </h3>
            <p className="text-xs text-green-400/60">
              Please review and respond
            </p>
          </div>
        </div>
        <button
          onClick={() => setMinimized(true)}
          className="px-2 py-1 text-xs font-mono text-green-400/70 hover:text-green-300 border border-green-500/30 hover:border-green-400/50 rounded transition-colors"
          title="Minimize (question will remain pending)"
        >
          Minimize
        </button>
      </div>

      {/* Content */}
      <div className="p-4 space-y-4">
        {/* Context */}
        {question.context && (
          <div className="text-xs text-green-300/70 font-mono uppercase tracking-wide">
            {question.context}
          </div>
        )}

        {/* Question */}
        <div className="text-green-100 font-mono text-base leading-relaxed">
          {question.question}
        </div>

        {/* Choices */}
        <div className="flex flex-wrap gap-3 pt-2">
          {defaultChoices.map((choice, idx) => (
            <button
              key={idx}
              onClick={() => handleAnswer(choice)}
              disabled={submitting}
              className={`px-4 py-2.5 font-mono text-sm border rounded-md transition-all disabled:opacity-50 disabled:cursor-not-allowed ${
                idx === 0
                  ? "bg-green-500/20 border-green-400/60 hover:bg-green-500/30 text-green-100 hover:border-green-400"
                  : "bg-amber-500/20 border-amber-400/60 hover:bg-amber-500/30 text-amber-100 hover:border-amber-400"
              }`}
            >
              {submitting ? "..." : choice}
            </button>
          ))}
        </div>

        {/* Custom input option */}
        {!showCustom ? (
          <button
            onClick={() => setShowCustom(true)}
            className="text-xs text-green-400/70 hover:text-green-300 font-mono transition-colors"
          >
            + Provide custom response
          </button>
        ) : (
          <div className="flex gap-2 pt-2">
            <input
              type="text"
              value={customInput}
              onChange={(e) => setCustomInput(e.target.value)}
              placeholder="Type your response..."
              disabled={submitting}
              className="flex-1 px-3 py-2 bg-[#0a1525] border border-green-500/30 text-green-100 text-sm font-mono rounded focus:outline-none focus:border-green-400"
              onKeyDown={(e) => {
                if (e.key === "Enter" && customInput.trim()) {
                  handleCustomSubmit();
                }
                if (e.key === "Escape") {
                  setShowCustom(false);
                  setCustomInput("");
                }
              }}
            />
            <button
              onClick={handleCustomSubmit}
              disabled={submitting || !customInput.trim()}
              className="px-4 py-2 bg-green-500 text-black text-sm font-medium rounded disabled:opacity-50 disabled:cursor-not-allowed transition-colors hover:bg-green-400"
            >
              {submitting ? "..." : "Submit"}
            </button>
            <button
              onClick={() => {
                setShowCustom(false);
                setCustomInput("");
              }}
              className="px-2 py-2 text-green-400/70 hover:text-green-300 text-sm font-mono"
            >
              Cancel
            </button>
          </div>
        )}
      </div>
    </div>
  );
}