← back to Letsbegin

components/SimplifiedQuestionnaire.tsx

629 lines

'use client'

import { useState } from 'react'
import { Project, PRDData } from '@/app/page'

interface Question {
  id: string
  title: string
  subtitle: string
  field: keyof PRDData | 'competitorUrls' | 'appStoreUrls' | 'researchOptions'
  type: 'text' | 'textarea' | 'select' | 'multiselect' | 'url-list' | 'checkbox-grid'
  options?: { value: string; label: string; icon?: string; desc?: string }[]
  placeholder?: string
  required?: boolean
}

const questions: Question[] = [
  {
    id: 'projectName',
    title: 'What project are you working on?',
    subtitle: 'Select the project from your Projects folder',
    field: 'projectName',
    type: 'text',
    placeholder: 'Project name...',
    required: true
  },
  {
    id: 'featureTitle',
    title: 'What feature are you building?',
    subtitle: 'Give your feature a short, descriptive title',
    field: 'featureTitle',
    type: 'text',
    placeholder: 'e.g., User Authentication System',
    required: true
  },
  {
    id: 'problemStatement',
    title: 'What problem does this solve?',
    subtitle: 'Describe the issue or need this feature addresses',
    field: 'problemStatement',
    type: 'textarea',
    placeholder: 'Users currently have to... but they need...',
    required: true
  },
  {
    id: 'targetUser',
    title: 'Who is this for?',
    subtitle: 'Select the primary user type',
    field: 'targetUser',
    type: 'select',
    options: [
      { value: 'A', label: 'New users - First time visitors' },
      { value: 'B', label: 'Existing users - Regular customers' },
      { value: 'C', label: 'Admin users - Internal team' },
      { value: 'D', label: 'All users - Everyone' }
    ]
  },
  {
    id: 'scope',
    title: 'How big is this feature?',
    subtitle: 'Choose the scope of implementation',
    field: 'scope',
    type: 'select',
    options: [
      { value: 'A', label: 'Minimal - Quick MVP, bare essentials' },
      { value: 'B', label: 'Full-featured - Complete implementation' },
      { value: 'C', label: 'Backend only - APIs and data' },
      { value: 'D', label: 'Frontend only - UI components' }
    ]
  },
  {
    id: 'priority',
    title: 'How urgent is this?',
    subtitle: 'Set the priority level',
    field: 'priority',
    type: 'select',
    options: [
      { value: 'A', label: 'Critical - ASAP, blocking other work' },
      { value: 'B', label: 'High - Important, do soon' },
      { value: 'C', label: 'Medium - Standard priority' },
      { value: 'D', label: 'Low - Nice to have' }
    ]
  },
  {
    id: 'keyFeatures',
    title: 'What are the key features?',
    subtitle: 'List the main capabilities (one per line)',
    field: 'keyFeatures',
    type: 'textarea',
    placeholder: '• User can sign up with email\n• Password reset via email\n• Remember me option'
  },
  {
    id: 'nonGoals',
    title: 'What should it NOT do?',
    subtitle: 'Define boundaries and exclusions',
    field: 'nonGoals',
    type: 'textarea',
    placeholder: '• No social login in v1\n• No 2FA initially'
  },
  {
    id: 'successMetrics',
    title: 'How will you measure success?',
    subtitle: 'Define specific, measurable outcomes',
    field: 'successMetrics',
    type: 'textarea',
    placeholder: '• Login takes < 2 seconds\n• 99% uptime\n• Zero security breaches'
  },
  {
    id: 'integrations',
    title: 'What integrations are needed?',
    subtitle: 'APIs, services, or third-party tools',
    field: 'integrations',
    type: 'textarea',
    placeholder: 'e.g., Stripe for payments, SendGrid for email'
  },
  {
    id: 'techStack',
    title: 'What tech stack?',
    subtitle: 'Framework and technology preferences',
    field: 'techStack',
    type: 'text',
    placeholder: 'Next.js 16 + React 19'
  },
  {
    id: 'competitorUrls',
    title: 'Any apps or websites for inspiration?',
    subtitle: 'Add URLs to similar apps or competitors',
    field: 'competitorUrls',
    type: 'url-list',
    placeholder: 'https://example.com'
  },
  {
    id: 'appStoreUrls',
    title: 'Similar mobile apps?',
    subtitle: 'Links to App Store or Play Store apps for reference',
    field: 'appStoreUrls',
    type: 'url-list',
    placeholder: 'https://apps.apple.com/...'
  },
  {
    id: 'researchOptions',
    title: 'What should we research?',
    subtitle: 'Select sources to explore for inspiration',
    field: 'researchOptions',
    type: 'checkbox-grid',
    options: [
      { value: 'searchGithub', label: 'GitHub', icon: '📦', desc: 'Similar projects' },
      { value: 'searchPublicDBs', label: 'Public APIs', icon: '🗄️', desc: 'APIs & datasets' },
      { value: 'searchSkills', label: 'Skills', icon: '🎯', desc: 'Reusable skills' },
      { value: 'searchAgents', label: 'Agents', icon: '🤖', desc: 'Pre-built agents' },
      { value: 'searchAppStore', label: 'App Store', icon: '🍎', desc: 'iOS apps' },
      { value: 'searchPlayStore', label: 'Play Store', icon: '📱', desc: 'Android apps' }
    ]
  }
]

interface Props {
  prdData: PRDData
  setPrdData: React.Dispatch<React.SetStateAction<PRDData>>
  selectedProject: Project | null
  onComplete: () => void
  onCancel: () => void
}

export default function SimplifiedQuestionnaire({
  prdData,
  setPrdData,
  selectedProject,
  onComplete,
  onCancel
}: Props) {
  const [currentIndex, setCurrentIndex] = useState(0)
  const [suggestion, setSuggestion] = useState<string | null>(null)
  const [isLoadingSuggestion, setIsLoadingSuggestion] = useState(false)
  const [urlInfoLoading, setUrlInfoLoading] = useState<Record<number, boolean>>({})
  const [urlInfo, setUrlInfo] = useState<Record<number, { title: string; description: string; features: string[] } | null>>({})

  const handleLoadUrlInfo = async (url: string, index: number) => {
    if (!url || !url.startsWith('http')) return

    setUrlInfoLoading(prev => ({ ...prev, [index]: true }))
    try {
      const res = await fetch('/api/url-info', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ url })
      })

      if (res.ok) {
        const data = await res.json()
        setUrlInfo(prev => ({ ...prev, [index]: data }))
      } else {
        setUrlInfo(prev => ({ ...prev, [index]: null }))
      }
    } catch (err) {
      console.error('URL info error:', err)
      setUrlInfo(prev => ({ ...prev, [index]: null }))
    } finally {
      setUrlInfoLoading(prev => ({ ...prev, [index]: false }))
    }
  }

  const handleAddToField = (info: { title: string; description: string; features: string[] }, field: 'keyFeatures' | 'problemStatement') => {
    if (field === 'keyFeatures') {
      const existing = prdData.keyFeatures || ''
      const newFeatures = info.features.map(f => `• ${f}`).join('\n')
      const combined = existing ? `${existing}\n\n📌 From ${info.title}:\n${newFeatures}` : `📌 From ${info.title}:\n${newFeatures}`
      setPrdData(prev => ({ ...prev, keyFeatures: combined }))
    } else if (field === 'problemStatement') {
      const existing = prdData.problemStatement || ''
      const combined = existing ? `${existing}\n\n📌 Reference (${info.title}): ${info.description}` : `📌 Reference (${info.title}): ${info.description}`
      setPrdData(prev => ({ ...prev, problemStatement: combined }))
    }
  }

  const currentQuestion = questions[currentIndex]
  const progress = ((currentIndex + 1) / questions.length) * 100

  const handleChange = (value: string | string[]) => {
    setPrdData(prev => ({
      ...prev,
      [currentQuestion.field]: value
    }))
  }

  const getValue = (): string => {
    const val = prdData[currentQuestion.field as keyof PRDData]
    if (Array.isArray(val)) return val.join(', ')
    if (typeof val === 'object' && val !== null) return '' // For researchOptions
    return (val as string) || ''
  }

  const canProceed = () => {
    if (!currentQuestion.required) return true
    // URL lists and checkbox grids are optional
    if (currentQuestion.type === 'url-list' || currentQuestion.type === 'checkbox-grid') return true
    const value = getValue()
    return value.trim().length > 0
  }

  const handleNext = () => {
    setSuggestion(null)
    if (currentIndex < questions.length - 1) {
      setCurrentIndex(currentIndex + 1)
    } else {
      onComplete()
    }
  }

  const handleBack = () => {
    setSuggestion(null)
    if (currentIndex > 0) {
      setCurrentIndex(currentIndex - 1)
    }
  }

  const handleGetSuggestion = async () => {
    setIsLoadingSuggestion(true)
    try {
      const context = {
        projectName: prdData.projectName || selectedProject?.name || 'Unknown Project',
        currentField: currentQuestion.field,
        currentQuestion: currentQuestion.title,
        existingData: prdData
      }

      const res = await fetch('/api/claude/suggest', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(context)
      })

      if (res.ok) {
        const data = await res.json()
        setSuggestion(data.suggestion)
      } else {
        setSuggestion('Unable to get suggestion. Please try again.')
      }
    } catch (err) {
      console.error('Suggestion error:', err)
      setSuggestion('Error getting suggestion. Check your connection.')
    } finally {
      setIsLoadingSuggestion(false)
    }
  }

  const applySuggestion = () => {
    if (suggestion) {
      // For url-list fields, parse the suggestion to extract URLs
      if (currentQuestion.type === 'url-list') {
        // Extract URLs from the formatted suggestion
        // Matches URLs like https://example.com or http://example.com
        const urlRegex = /https?:\/\/[^\s\)]+/g
        const matches = suggestion.match(urlRegex)
        if (matches && matches.length > 0) {
          // Clean up URLs (remove trailing punctuation)
          const urls = matches.map(url => url.replace(/[,\.\)\]]+$/, ''))
          handleChange(urls)
        } else {
          // Fallback: split by newlines and filter for URL-like strings
          const lines = suggestion.split('\n').filter(line => line.includes('http'))
          const urls = lines.map(line => {
            const match = line.match(/https?:\/\/[^\s\)]+/)
            return match ? match[0].replace(/[,\.\)\]]+$/, '') : ''
          }).filter(Boolean)
          if (urls.length > 0) {
            handleChange(urls)
          }
        }
      } else {
        handleChange(suggestion)
      }
      setSuggestion(null)
    }
  }

  return (
    <div className="fixed inset-0 bg-[var(--background)] z-[9999] flex flex-col overflow-y-auto">
      {/* Progress bar */}
      <div className="h-2 bg-[var(--surface-alt)] flex-shrink-0">
        <div
          className="h-full bg-gradient-to-r from-[var(--primary)] to-[var(--secondary)] transition-all duration-300"
          style={{ width: `${progress}%` }}
        />
      </div>

      {/* Header - centered */}
      <div className="relative flex items-center justify-center px-8 py-4 border-b border-[var(--border)] flex-shrink-0">
        <button
          onClick={onCancel}
          className="absolute left-8 text-[var(--foreground-secondary)] hover:text-[var(--foreground)] transition-colors"
        >
          <svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
          </svg>
        </button>
        <div className="text-lg font-medium text-[var(--foreground)]">
          Question {currentIndex + 1} of {questions.length}
        </div>
      </div>

      {/* Main content - fully centered with max-width container */}
      <div className="flex-1 flex flex-col items-center justify-center px-4 md:px-8 py-8 w-full">
        <div className="w-full max-w-2xl mx-auto flex flex-col items-center">
        {/* Question title */}
        <h1 className="text-4xl md:text-5xl font-bold text-center mb-4 leading-tight">
          {currentQuestion.title}
        </h1>

        {/* Subtitle */}
        <p className="text-xl text-[var(--foreground-secondary)] text-center mb-8">
          {currentQuestion.subtitle}
        </p>

        {/* Input field */}
        <div className="w-full">
          {currentQuestion.type === 'text' && (
            <input
              type="text"
              value={getValue()}
              onChange={(e) => handleChange(e.target.value)}
              placeholder={currentQuestion.placeholder}
              className="w-full text-2xl p-6 rounded-xl bg-[var(--surface)] border-2 border-[var(--border)] focus:border-[var(--primary)] focus:outline-none transition-colors text-center"
              autoFocus
            />
          )}

          {currentQuestion.type === 'textarea' && (
            <textarea
              value={getValue()}
              onChange={(e) => handleChange(e.target.value)}
              placeholder={currentQuestion.placeholder}
              rows={5}
              className="w-full text-xl p-6 rounded-xl bg-[var(--surface)] border-2 border-[var(--border)] focus:border-[var(--primary)] focus:outline-none transition-colors resize-none"
              autoFocus
            />
          )}

          {currentQuestion.type === 'select' && currentQuestion.options && (
            <div className="space-y-3">
              {currentQuestion.options.map((option) => (
                <button
                  key={option.value}
                  onClick={() => handleChange(option.value)}
                  className={`w-full text-left p-5 rounded-xl border-2 transition-all ${
                    getValue() === option.value
                      ? 'border-[var(--primary)] bg-[var(--primary)]/10'
                      : 'border-[var(--border)] bg-[var(--surface)] hover:border-[var(--primary)]/50'
                  }`}
                >
                  <span className="text-xl font-medium">{option.label}</span>
                </button>
              ))}
            </div>
          )}

          {currentQuestion.type === 'url-list' && (
            <div className="space-y-4">
              {((prdData[currentQuestion.field as keyof PRDData] as string[]) || ['']).map((url, index) => (
                <div key={index} className="space-y-2">
                  <div className="flex gap-2">
                    <input
                      type="url"
                      value={url}
                      onChange={(e) => {
                        const urls = [...((prdData[currentQuestion.field as keyof PRDData] as string[]) || [''])]
                        urls[index] = e.target.value
                        setPrdData(prev => ({ ...prev, [currentQuestion.field]: urls }))
                        // Clear loaded info when URL changes
                        setUrlInfo(prev => ({ ...prev, [index]: null }))
                      }}
                      placeholder={currentQuestion.placeholder}
                      className="flex-1 text-xl p-4 rounded-xl bg-[var(--surface)] border-2 border-[var(--border)] focus:border-[var(--primary)] focus:outline-none transition-colors"
                      autoFocus={index === 0}
                    />
                    {/* Load Info Button */}
                    {url && url.startsWith('http') && (
                      <button
                        onClick={() => handleLoadUrlInfo(url, index)}
                        disabled={urlInfoLoading[index]}
                        className="px-4 py-2 rounded-xl bg-gradient-to-r from-green-500/20 to-emerald-500/20 border border-green-500/30 text-green-400 font-medium hover:from-green-500/30 hover:to-emerald-500/30 disabled:opacity-50 transition-all whitespace-nowrap"
                      >
                        {urlInfoLoading[index] ? (
                          <span className="flex items-center gap-2">
                            <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
                              <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
                              <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
                            </svg>
                            Loading...
                          </span>
                        ) : urlInfo[index] ? '✓ Loaded' : '🔍 Load'}
                      </button>
                    )}
                    {index === ((prdData[currentQuestion.field as keyof PRDData] as string[]) || ['']).length - 1 ? (
                      <button
                        onClick={() => {
                          const urls = [...((prdData[currentQuestion.field as keyof PRDData] as string[]) || [''])]
                          setPrdData(prev => ({ ...prev, [currentQuestion.field]: [...urls, ''] }))
                        }}
                        className="px-4 py-2 rounded-xl bg-[var(--primary)] text-white font-medium hover:opacity-90"
                      >
                        + Add
                      </button>
                    ) : (
                      <button
                        onClick={() => {
                          const urls = ((prdData[currentQuestion.field as keyof PRDData] as string[]) || ['']).filter((_, i) => i !== index)
                          setPrdData(prev => ({ ...prev, [currentQuestion.field]: urls.length ? urls : [''] }))
                          // Clear loaded info for removed URL
                          setUrlInfo(prev => {
                            const newInfo = { ...prev }
                            delete newInfo[index]
                            return newInfo
                          })
                        }}
                        className="px-4 py-2 rounded-xl bg-red-500/20 text-red-400 font-medium hover:bg-red-500/30"
                      >
                        Remove
                      </button>
                    )}
                  </div>

                  {/* Loaded URL Info Display */}
                  {urlInfo[index] && (
                    <div className="ml-2 p-4 rounded-xl bg-[var(--surface-alt)] border border-[var(--border)]">
                      <div className="flex items-start justify-between gap-4">
                        <div className="flex-1 min-w-0">
                          <h4 className="font-semibold text-lg text-[var(--foreground)] truncate">{urlInfo[index]?.title || 'Untitled'}</h4>
                          <p className="text-sm text-[var(--foreground-secondary)] mt-1 line-clamp-2">{urlInfo[index]?.description || 'No description'}</p>
                          {urlInfo[index]?.features && urlInfo[index]!.features.length > 0 && (
                            <div className="mt-2 flex flex-wrap gap-1">
                              {urlInfo[index]!.features.slice(0, 5).map((f, i) => (
                                <span key={i} className="px-2 py-1 text-xs rounded-full bg-[var(--primary)]/20 text-[var(--primary)]">{f}</span>
                              ))}
                              {urlInfo[index]!.features.length > 5 && (
                                <span className="px-2 py-1 text-xs rounded-full bg-[var(--border)] text-[var(--foreground-tertiary)]">+{urlInfo[index]!.features.length - 5} more</span>
                              )}
                            </div>
                          )}
                        </div>
                        <div className="flex flex-col gap-2">
                          <button
                            onClick={() => handleAddToField(urlInfo[index]!, 'keyFeatures')}
                            className="px-3 py-2 text-sm rounded-lg bg-blue-500/20 text-blue-400 hover:bg-blue-500/30 transition-colors whitespace-nowrap"
                          >
                            + Add to Features
                          </button>
                          <button
                            onClick={() => handleAddToField(urlInfo[index]!, 'problemStatement')}
                            className="px-3 py-2 text-sm rounded-lg bg-purple-500/20 text-purple-400 hover:bg-purple-500/30 transition-colors whitespace-nowrap"
                          >
                            + Add to Problem
                          </button>
                        </div>
                      </div>
                    </div>
                  )}
                </div>
              ))}

              {/* Search Claude for competitor suggestions button */}
              <button
                onClick={handleGetSuggestion}
                disabled={isLoadingSuggestion}
                className="w-full mt-4 flex items-center justify-center gap-3 px-6 py-4 rounded-xl bg-gradient-to-r from-purple-500/20 to-blue-500/20 border-2 border-purple-500/30 text-purple-300 hover:from-purple-500/30 hover:to-blue-500/30 hover:border-purple-500/50 transition-all disabled:opacity-50"
              >
                {isLoadingSuggestion ? (
                  <>
                    <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
                      <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
                      <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
                    </svg>
                    <span className="text-lg font-medium">Searching for inspiration...</span>
                  </>
                ) : (
                  <>
                    <span className="text-2xl">🔍</span>
                    <span className="text-lg font-medium">Search Claude for Suggestions</span>
                  </>
                )}
              </button>

              <p className="text-sm text-[var(--foreground-tertiary)] text-center mt-2">
                Skip this step if you don't have any URLs
              </p>
            </div>
          )}

          {currentQuestion.type === 'checkbox-grid' && currentQuestion.options && (
            <div className="grid grid-cols-2 md:grid-cols-3 gap-4">
              {currentQuestion.options.map((option) => {
                const researchOpts = prdData.researchOptions || {}
                const isChecked = researchOpts[option.value as keyof typeof researchOpts] || false
                return (
                  <button
                    key={option.value}
                    onClick={() => {
                      setPrdData(prev => ({
                        ...prev,
                        researchOptions: {
                          ...prev.researchOptions,
                          [option.value]: !isChecked
                        }
                      }))
                    }}
                    className={`p-4 rounded-xl border-2 transition-all text-left ${
                      isChecked
                        ? 'border-[var(--primary)] bg-[var(--primary)]/10'
                        : 'border-[var(--border)] bg-[var(--surface)] hover:border-[var(--primary)]/50'
                    }`}
                  >
                    <div className="flex items-center gap-3">
                      <span className="text-2xl">{option.icon}</span>
                      <div>
                        <div className="font-medium">{option.label}</div>
                        <div className="text-sm text-[var(--foreground-tertiary)]">{option.desc}</div>
                      </div>
                    </div>
                  </button>
                )
              })}
            </div>
          )}
        </div>

        {/* Claude suggestion button */}
        <button
          onClick={handleGetSuggestion}
          disabled={isLoadingSuggestion}
          className="mt-8 flex items-center gap-2 px-6 py-3 rounded-full bg-gradient-to-r from-blue-500/20 to-purple-500/20 border border-blue-500/30 text-blue-400 hover:from-blue-500/30 hover:to-purple-500/30 transition-all disabled:opacity-50"
        >
          {isLoadingSuggestion ? (
            <>
              <svg className="w-5 h-5 animate-spin" fill="none" viewBox="0 0 24 24">
                <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
                <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
              </svg>
              <span>Thinking...</span>
            </>
          ) : (
            <>
              <span className="text-lg">💡</span>
              <span className="font-medium">Get Claude's Suggestion</span>
            </>
          )}
        </button>

        {/* Suggestion box */}
        {suggestion && (
          <div className="mt-6 w-full max-w-xl p-5 rounded-xl bg-blue-500/10 border border-blue-500/30">
            <div className="flex items-start gap-3">
              <span className="text-2xl">💡</span>
              <div className="flex-1">
                <p className="text-blue-300 whitespace-pre-wrap">{suggestion}</p>
                <button
                  onClick={applySuggestion}
                  className="mt-3 px-4 py-2 rounded-lg bg-blue-500 text-white font-medium hover:bg-blue-600 transition-colors"
                >
                  Use This Suggestion
                </button>
              </div>
            </div>
          </div>
        )}

        {/* Navigation buttons - underneath input field */}
        <div className="flex items-center justify-center gap-4 mt-10 w-full">
          <button
            onClick={handleBack}
            disabled={currentIndex === 0}
            className="flex-1 max-w-[200px] px-6 py-4 rounded-xl text-lg font-medium border-2 border-[var(--border)] text-[var(--foreground-secondary)] hover:text-[var(--foreground)] hover:border-[var(--foreground-secondary)] disabled:opacity-30 disabled:cursor-not-allowed transition-all"
          >
            ← Back
          </button>

          <button
            onClick={handleNext}
            disabled={!canProceed()}
            className="flex-1 max-w-[200px] px-8 py-4 rounded-xl text-lg font-medium bg-gradient-to-r from-[var(--primary)] to-[var(--secondary)] text-white hover:opacity-90 disabled:opacity-30 disabled:cursor-not-allowed transition-all"
          >
            {currentIndex === questions.length - 1 ? '📄 Submit' : 'Next →'}
          </button>
        </div>
        </div>
      </div>
    </div>
  )
}