← back to Letsbegin

components/QuickModeStep.tsx

442 lines

'use client'

import { useState, useEffect } from 'react'
import { Zap, ArrowRight, Loader2, ChevronDown, ChevronRight, Clock, Trash2 } from 'lucide-react'
import { Project } from '@/app/page'

interface QuickModeEntry {
  id: number
  project_name: string
  project_path: string
  entry_text: string
  created_at: string
}

interface ClarifyingQuestion {
  id: string
  question: string
  options: { key: string; label: string }[]
  answer?: string
}

interface QuickModeStepProps {
  selectedProject: Project | null
  quickModeText: string
  onTextChange: (text: string) => void
  onGenerate: (prd: string) => void
}

export default function QuickModeStep({
  selectedProject,
  quickModeText,
  onTextChange,
  onGenerate
}: QuickModeStepProps) {
  const [isGenerating, setIsGenerating] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [recentEntries, setRecentEntries] = useState<QuickModeEntry[]>([])
  const [isHistoryExpanded, setIsHistoryExpanded] = useState(false)

  // Clarifying questions state
  const [step, setStep] = useState<'input' | 'clarifying' | 'generating'>('input')
  const [clarifyingQuestions, setClarifyingQuestions] = useState<ClarifyingQuestion[]>([])
  const [isLoadingQuestions, setIsLoadingQuestions] = useState(false)

  // Fetch recent entries on mount
  useEffect(() => {
    fetchRecentEntries()
  }, [])

  async function fetchRecentEntries() {
    try {
      const res = await fetch('/api/quickmode')
      const data = await res.json()
      if (data.entries) {
        setRecentEntries(data.entries)
      }
    } catch (err) {
      console.error('Failed to fetch recent entries:', err)
    }
  }

  async function saveEntry() {
    if (!selectedProject || !quickModeText.trim()) return

    try {
      await fetch('/api/quickmode', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          projectName: selectedProject.name,
          projectPath: selectedProject.path,
          entryText: quickModeText
        })
      })
      // Refresh the list
      fetchRecentEntries()
    } catch (err) {
      console.error('Failed to save entry:', err)
    }
  }

  // Get clarifying questions first
  async function handleSubmit() {
    if (!selectedProject || !quickModeText.trim()) return

    setIsLoadingQuestions(true)
    setError(null)

    // Save to database first
    await saveEntry()

    try {
      // Extract a title from the first line or first 50 chars
      const firstLine = quickModeText.split('\n')[0].trim()
      const featureTitle = firstLine.length > 50
        ? firstLine.substring(0, 50) + '...'
        : firstLine || 'Quick Mode Task'

      // Call clarify endpoint to get questions
      const res = await fetch('/api/prd/clarify', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          projectName: selectedProject.name,
          featureTitle,
          problemStatement: quickModeText,
          targetUser: 'D', // All users
          scope: 'B', // Full-featured
          priority: 'B', // High priority
          keyFeatures: quickModeText,
          nonGoals: '',
          successMetrics: '',
          components: [],
          integrations: '',
          techStack: 'Existing project stack',
          additionalAnswers: '',
          projectPath: selectedProject.path
        })
      })

      const data = await res.json()

      if (data.questions && data.questions.length > 0) {
        setClarifyingQuestions(data.questions)
        setStep('clarifying')
      } else {
        // No questions, go straight to generate
        await generatePRD([])
      }
    } catch (err) {
      setError('Failed to get clarifying questions. Please try again.')
      console.error('Quick mode clarify error:', err)
    } finally {
      setIsLoadingQuestions(false)
    }
  }

  // Handle answering a clarifying question
  function handleClarifyingAnswer(questionId: string, answer: string) {
    setClarifyingQuestions(prev =>
      prev.map(q => q.id === questionId ? { ...q, answer } : q)
    )
  }

  // Generate PRD with answers
  async function generatePRD(answers: ClarifyingQuestion[]) {
    if (!selectedProject) return

    setStep('generating')
    setIsGenerating(true)
    setError(null)

    try {
      const firstLine = quickModeText.split('\n')[0].trim()
      const featureTitle = firstLine.length > 50
        ? firstLine.substring(0, 50) + '...'
        : firstLine || 'Quick Mode Task'

      // Format answers
      const formattedAnswers = answers
        .filter(q => q.answer)
        .map((q, i) => `${i + 1}${q.answer}: ${q.question} → ${q.options.find(o => o.key === q.answer)?.label || q.answer}`)
        .join('\n')

      const res = await fetch('/api/prd/generate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          projectName: selectedProject.name,
          featureTitle,
          problemStatement: quickModeText,
          targetUser: 'D', // All users
          scope: 'B', // Full-featured
          priority: 'B', // High priority
          keyFeatures: quickModeText,
          nonGoals: '',
          successMetrics: '',
          components: [],
          integrations: '',
          techStack: 'Existing project stack',
          additionalAnswers: formattedAnswers ? `Clarifying Questions:\n${formattedAnswers}` : '',
          projectPath: selectedProject.path
        })
      })

      const data = await res.json()

      if (data.error) {
        setError(data.error)
        setStep('input')
      } else if (data.prd) {
        onGenerate(data.prd)
      }
    } catch (err) {
      setError('Failed to generate PRD. Please try again.')
      console.error('Quick mode generation error:', err)
      setStep('input')
    } finally {
      setIsGenerating(false)
    }
  }

  // Submit clarifying answers
  function submitClarifyingAnswers() {
    const unanswered = clarifyingQuestions.filter(q => !q.answer)
    if (unanswered.length > 0) {
      setError(`Please answer all questions. ${unanswered.length} remaining.`)
      return
    }
    generatePRD(clarifyingQuestions)
  }

  function loadEntry(entry: QuickModeEntry) {
    onTextChange(entry.entry_text)
  }

  function formatDate(dateStr: string) {
    const date = new Date(dateStr)
    return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
  }

  function truncateText(text: string, maxLength: number = 80) {
    if (text.length <= maxLength) return text
    return text.substring(0, maxLength) + '...'
  }

  return (
    <div className="space-y-6">
      {/* Header */}
      <div className="flex items-center gap-3">
        <div className="p-2 rounded-lg bg-[var(--primary)]/20">
          <Zap className="w-5 h-5 text-[var(--primary)]" />
        </div>
        <div>
          <h2 className="text-xl font-semibold text-[var(--foreground)]">Quick Mode</h2>
          <p className="text-sm text-[var(--foreground-secondary)]">
            {step === 'input' && 'Describe your task, answer clarifying questions, then generate PRD'}
            {step === 'clarifying' && 'Answer these questions to help generate a better PRD'}
            {step === 'generating' && 'Generating your PRD...'}
          </p>
        </div>
      </div>

      {/* Project Status */}
      {selectedProject ? (
        <div className="p-3 rounded-lg border border-[var(--border)] bg-[var(--surface-alt)]">
          <p className="text-sm text-[var(--foreground-secondary)]">
            Working on: <span className="font-medium text-[var(--primary)]">{selectedProject.name}</span>
          </p>
        </div>
      ) : (
        <div className="p-3 rounded-lg border border-yellow-500/30 bg-yellow-500/10">
          <p className="text-sm text-yellow-400">
            Select a project from the dropdown above to get started
          </p>
        </div>
      )}

      {/* Error Message */}
      {error && (
        <div className="p-3 rounded-lg border border-red-500/30 bg-red-500/10">
          <p className="text-sm text-red-400">{error}</p>
        </div>
      )}

      {/* Step: Input */}
      {step === 'input' && (
        <>
          {/* Text Area */}
          <div className="space-y-2">
            <label className="text-sm font-medium text-[var(--foreground)]">
              What do you want to do?
            </label>
            <textarea
              value={quickModeText}
              onChange={(e) => onTextChange(e.target.value)}
              disabled={isLoadingQuestions}
              placeholder="Describe your task, feature, bug fix, or any work you need done...

Examples:
- Add a dark mode toggle to the settings page
- Fix the login form validation error
- Create an API endpoint for user profiles
- Refactor the database queries for better performance"
              className="w-full h-64 p-4 rounded-lg border border-[var(--border)] bg-[var(--background)] text-[var(--foreground)] placeholder:text-[var(--foreground-tertiary)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] focus:border-transparent resize-none disabled:opacity-50"
            />
            <p className="text-xs text-[var(--foreground-tertiary)]">
              {quickModeText.length} characters
            </p>
          </div>

          {/* Submit Button */}
          <div className="flex justify-end">
            <button
              onClick={handleSubmit}
              disabled={!selectedProject || !quickModeText.trim() || isLoadingQuestions}
              className="flex items-center gap-2 px-6 py-3 rounded-lg bg-[var(--primary)] text-white font-medium hover:bg-[var(--primary)]/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
            >
              {isLoadingQuestions ? (
                <>
                  <Loader2 className="w-4 h-4 animate-spin" />
                  Getting Questions...
                </>
              ) : (
                <>
                  Next: Clarifying Questions
                  <ArrowRight className="w-4 h-4" />
                </>
              )}
            </button>
          </div>
        </>
      )}

      {/* Step: Clarifying Questions */}
      {step === 'clarifying' && (
        <div className="card p-6 border border-[var(--border)] rounded-xl bg-[var(--surface)]">
          <div className="mb-6">
            <h3 className="text-xl font-semibold flex items-center gap-2 mb-2">
              <span>🤔</span>
              Clarifying Questions
            </h3>
            <p className="text-[var(--foreground-secondary)] text-sm">
              Based on your input, please answer these questions. Select the best option (A, B, C, or D).
            </p>
          </div>

          <div className="space-y-8">
            {clarifyingQuestions.map((q, index) => (
              <div key={q.id} className="border-b border-[var(--border)] pb-6 last:border-b-0 last:pb-0">
                <div className="font-bold text-lg mb-4">
                  Q{index + 1}: {q.question}
                </div>
                <div className="space-y-2 pl-4">
                  {q.options.map((opt) => (
                    <button
                      key={opt.key}
                      onClick={() => handleClarifyingAnswer(q.id, opt.key)}
                      className={`w-full p-3 rounded-lg border text-left transition-all flex items-start gap-3 ${
                        q.answer === opt.key
                          ? 'bg-[var(--primary)]/20 border-[var(--primary)]'
                          : 'bg-[var(--background)] border-[var(--border)] hover:border-[var(--primary)]/50'
                      }`}
                    >
                      <span className={`font-bold min-w-[24px] ${q.answer === opt.key ? 'text-[var(--primary)]' : ''}`}>
                        {opt.key})
                      </span>
                      <span className="text-[var(--foreground-secondary)]">{opt.label}</span>
                    </button>
                  ))}
                </div>
              </div>
            ))}
          </div>

          <div className="flex items-center justify-between mt-6 pt-4 border-t border-[var(--border)]">
            <button
              onClick={() => setStep('input')}
              className="px-4 py-2 rounded-lg border border-[var(--border)] text-[var(--foreground-secondary)] hover:bg-[var(--surface-alt)] transition-colors"
            >
              ← Back
            </button>
            <div className="flex items-center gap-2 text-sm text-[var(--foreground-secondary)]">
              <span>
                {clarifyingQuestions.filter(q => q.answer).length}/{clarifyingQuestions.length} answered
              </span>
            </div>
            <button
              onClick={submitClarifyingAnswers}
              disabled={clarifyingQuestions.some(q => !q.answer)}
              className="flex items-center gap-2 px-6 py-3 rounded-lg bg-[var(--primary)] text-white font-medium hover:bg-[var(--primary)]/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
            >
              Generate PRD
              <ArrowRight className="w-4 h-4" />
            </button>
          </div>
        </div>
      )}

      {/* Step: Generating */}
      {step === 'generating' && (
        <div className="card p-8 text-center border border-[var(--border)] rounded-xl bg-[var(--surface)]">
          <div className="flex flex-col items-center gap-4">
            <Loader2 className="w-12 h-12 animate-spin text-[var(--primary)]" />
            <div className="text-xl font-medium">Generating PRD...</div>
            <p className="text-[var(--foreground-secondary)]">
              Claude is creating your Product Requirements Document based on your inputs.
            </p>
          </div>
        </div>
      )}

      {/* Recent Entries - Collapsible (only show in input step) */}
      {step === 'input' && recentEntries.length > 0 && (
        <div className="border-t border-[var(--border)] pt-4">
          <button
            onClick={() => setIsHistoryExpanded(!isHistoryExpanded)}
            className="flex items-center gap-2 text-sm text-[var(--foreground-secondary)] hover:text-[var(--foreground)] transition-colors w-full"
          >
            {isHistoryExpanded ? (
              <ChevronDown className="w-4 h-4" />
            ) : (
              <ChevronRight className="w-4 h-4" />
            )}
            <Clock className="w-4 h-4" />
            <span>Recent Entries ({recentEntries.length})</span>
          </button>

          {isHistoryExpanded && (
            <div className="mt-3 space-y-2">
              {recentEntries.map((entry) => (
                <div
                  key={entry.id}
                  onClick={() => loadEntry(entry)}
                  className="p-3 rounded-lg border border-[var(--border)] bg-[var(--background)] hover:border-[var(--primary)] cursor-pointer transition-colors group"
                >
                  <div className="flex items-start justify-between gap-2">
                    <div className="flex-1 min-w-0">
                      <p className="text-sm text-[var(--foreground)] truncate">
                        {truncateText(entry.entry_text)}
                      </p>
                      <div className="flex items-center gap-2 mt-1">
                        <span className="text-xs text-[var(--primary)]">{entry.project_name}</span>
                        <span className="text-xs text-[var(--foreground-tertiary)]">
                          {formatDate(entry.created_at)}
                        </span>
                      </div>
                    </div>
                    <span className="text-xs text-[var(--foreground-tertiary)] group-hover:text-[var(--primary)]">
                      Click to load
                    </span>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      )}
    </div>
  )
}