← back to Letsbegin

app/page.tsx

410 lines

'use client'

import { useState, useEffect } from 'react'
import ProjectSelector from '@/components/ProjectSelector'
import DirectoryTree from '@/components/DirectoryTree'
import PRDGeneratorStep from '@/components/PRDGeneratorStep'
import RalphConverterStep from '@/components/RalphConverterStep'
import RalphyBoyStep from '@/components/RalphyBoyStep'
import QuickModeStep from '@/components/QuickModeStep'
import ProcessStatusBar from '@/components/ProcessStatusBar'
import RecentItemsFooter, { addRecentItem, RecentItem } from '@/components/RecentItemsFooter'
import { useAuth } from '@/components/AuthProvider'
import { SidebarDemo } from '@/components/ui/SidebarDemo'
import { FolderOpen, ChevronDown, ChevronRight, Activity } from 'lucide-react'
import Link from 'next/link'

export interface Project {
  name: string
  path: string
  hasClaudeMd: boolean
}

export interface DirectoryNode {
  name: string
  path: string
  type: 'file' | 'directory'
  children?: DirectoryNode[]
}

export interface PRDData {
  projectName: string
  featureTitle: string
  problemStatement: string
  targetUser: string
  scope: string
  priority: string
  keyFeatures: string
  nonGoals: string
  successMetrics: string
  components: string[]
  integrations: string
  techStack: string
  additionalAnswers: string
  competitorUrls: string[]
  appStoreUrls: string[]
  uploadedFiles: string[]
  researchOptions: {
    searchGithub: boolean
    searchPublicDBs: boolean
    searchSkills: boolean
    searchAgents: boolean
    searchAppStore: boolean
    searchPlayStore: boolean
  }
}

export interface UserStory {
  id: string
  title: string
  description: string
  acceptanceCriteria: string[]
  priority: number
  passes: boolean
  notes: string
}

export interface RalphData {
  project: string
  branchName: string
  description: string
  userStories: UserStory[]
}

export default function Home() {
  const { username, logout } = useAuth()
  const [projects, setProjects] = useState<Project[]>([])
  const [selectedProject, setSelectedProject] = useState<Project | null>(null)
  const [directoryTree, setDirectoryTree] = useState<DirectoryNode | null>(null)
  const [isTreeCollapsed, setIsTreeCollapsed] = useState(true)
  const [currentStep, setCurrentStep] = useState(0)
  const [stepStatuses, setStepStatuses] = useState<Record<number, string>>({
    0: 'active',
    1: 'pending',
    2: 'pending',
    3: 'pending'
  })

  // Quick Mode State
  const [quickModeText, setQuickModeText] = useState('')
  const [isQuickMode, setIsQuickMode] = useState(true)

  // PRD State
  const [prdData, setPrdData] = useState<PRDData>({
    projectName: '',
    featureTitle: '',
    problemStatement: '',
    targetUser: 'D',
    scope: 'B',
    priority: 'B',
    keyFeatures: '',
    nonGoals: '',
    successMetrics: '',
    components: [],
    integrations: '',
    techStack: 'Next.js 16 + React 19',
    additionalAnswers: '',
    competitorUrls: [''],
    appStoreUrls: [''],
    uploadedFiles: [],
    researchOptions: {
      searchGithub: false,
      searchPublicDBs: false,
      searchSkills: false,
      searchAgents: false,
      searchAppStore: false,
      searchPlayStore: false
    }
  })
  const [generatedPRD, setGeneratedPRD] = useState('')

  // Ralph State
  const [ralphData, setRalphData] = useState<RalphData | null>(null)
  const [validationResults, setValidationResults] = useState<Record<string, boolean>>({})

  // Ralphy Boy State
  const [isRunning, setIsRunning] = useState(false)
  const [currentStoryIndex, setCurrentStoryIndex] = useState(0)
  const [logs, setLogs] = useState<string[]>([])

  // Load projects on mount
  useEffect(() => {
    loadProjects()
  }, [])

  // Load directory tree when project selected
  useEffect(() => {
    if (selectedProject) {
      loadDirectoryTree(selectedProject.path)
      const defaultFeatureTitle = `New Feature for ${selectedProject.name}`
      setPrdData(prev => ({
        ...prev,
        projectName: selectedProject.name,
        featureTitle: defaultFeatureTitle
      }))
      // Track as recent item
      addRecentItem({
        name: selectedProject.name,
        path: selectedProject.path,
        type: 'project'
      })
    }
  }, [selectedProject])

  async function loadProjects() {
    try {
      const res = await fetch('/api/projects')
      const data = await res.json()
      setProjects(data.projects || [])
    } catch (err) {
      console.error('Failed to load projects:', err)
    }
  }

  async function loadDirectoryTree(path: string) {
    try {
      const res = await fetch(`/api/directory?path=${encodeURIComponent(path)}`)
      const data = await res.json()
      setDirectoryTree(data)
    } catch (err) {
      console.error('Failed to load directory:', err)
    }
  }

  function handleStepChange(step: number) {
    // Allow navigation to completed steps or current step
    if (step <= currentStep || stepStatuses[step] === 'completed' || stepStatuses[step] === 'active') {
      setCurrentStep(step)
    }
  }

  function handleStepComplete(step: number) {
    setStepStatuses(prev => ({
      ...prev,
      [step]: 'completed',
      [step + 1]: step < 3 ? 'active' : prev[step + 1]
    }))
    if (step < 3) {
      setCurrentStep(step + 1)
    }
  }

  function handlePRDGenerated(prd: string) {
    setGeneratedPRD(prd)
    // Mark both step 0 and step 1 as complete when PRD is generated
    setStepStatuses(prev => ({
      ...prev,
      0: 'completed',
      1: 'completed',
      2: 'active'
    }))
    setCurrentStep(2) // Go directly to Ralph Converter
  }

  function handleModeToggle(useQuickMode: boolean) {
    if (!useQuickMode && quickModeText.trim()) {
      // Switching to form mode - copy quick mode text to additional notes
      setPrdData(prev => ({
        ...prev,
        additionalAnswers: quickModeText + (prev.additionalAnswers ? '\n\n' + prev.additionalAnswers : '')
      }))
    }
    setIsQuickMode(useQuickMode)
    setCurrentStep(useQuickMode ? 0 : 1)
  }

  function handleRalphConverted(data: RalphData) {
    setRalphData(data)
    handleStepComplete(2)
  }

  const stepTitles: Record<number, string> = {
    0: 'Quick Mode',
    1: 'PRD Generator',
    2: 'Ralph Converter',
    3: 'Ralphy Boy Runner'
  }

  function handleRecentItemClick(item: RecentItem) {
    // Find and select the project
    const project = projects.find(p => p.path === item.path)
    if (project) {
      setSelectedProject(project)
    }
  }

  return (
    <SidebarDemo
      currentStep={currentStep}
      onStepChange={handleStepChange}
      onLogout={logout}
      username={username ?? undefined}
    >
      <main className="h-full flex flex-col bg-[var(--background)] overflow-hidden">
        {/* Top Bar with Project Selector */}
        <header className="flex-shrink-0 border-b border-[var(--border)] bg-[var(--surface)] px-6 py-3">
          <div className="flex items-center justify-between">
            <div className="flex items-center gap-4">
              <h2 className="text-lg font-semibold text-[var(--foreground)]">
                {stepTitles[currentStep]}
              </h2>
              {stepStatuses[currentStep] === 'completed' && (
                <span className="text-xs px-2 py-1 rounded-full bg-[var(--success)]/20 text-[var(--success)]">
                  ✓ Completed
                </span>
              )}
            </div>
            <div className="flex items-center gap-3">
              <Link
                href="/live"
                className="flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm bg-green-500/20 text-green-400 border border-green-500/30 hover:bg-green-500/30 transition-colors"
              >
                <Activity className="w-4 h-4" />
                <span className="hidden sm:inline">Live Processes</span>
                <span className="relative flex h-2 w-2">
                  <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
                  <span className="relative inline-flex rounded-full h-2 w-2 bg-green-500"></span>
                </span>
              </Link>
              <ProjectSelector
                projects={projects}
                selectedProject={selectedProject}
                onSelect={setSelectedProject}
                onCreateNew={() => {/* TODO */}}
              />
            </div>
          </div>

          {/* Project Banner */}
          {selectedProject && (
            <div className="mt-3 flex items-center gap-2 text-sm">
              <FolderOpen className="w-4 h-4 text-[var(--primary)]" />
              <span className="font-medium text-[var(--primary)]">{selectedProject.name}</span>
              <span className="text-[var(--foreground-tertiary)]">|</span>
              <span className="text-[var(--foreground-tertiary)] text-xs">{selectedProject.path}</span>
            </div>
          )}
        </header>

        {/* Main Content Area */}
        <div className="flex-1 overflow-auto">
          <div className="p-6">
            {/* Directory Navigator (Collapsible) */}
            <div className="mb-6">
              <button
                onClick={() => setIsTreeCollapsed(!isTreeCollapsed)}
                className="flex items-center gap-2 text-sm text-[var(--foreground-secondary)] hover:text-[var(--foreground)] transition-colors"
              >
                {isTreeCollapsed ? (
                  <ChevronRight className="w-4 h-4" />
                ) : (
                  <ChevronDown className="w-4 h-4" />
                )}
                <FolderOpen className="w-4 h-4" />
                <span>Directory Navigator</span>
                {selectedProject && (
                  <span className="text-[var(--foreground-tertiary)]">— {selectedProject.name}</span>
                )}
              </button>

              {!isTreeCollapsed && (
                <div className="mt-3 p-4 rounded-lg border border-[var(--border)] bg-[var(--surface)] max-h-64 overflow-y-auto">
                  {directoryTree ? (
                    <DirectoryTree
                      node={directoryTree}
                      depth={0}
                      selectedPath={selectedProject?.path || ''}
                    />
                  ) : (
                    <p className="text-[var(--foreground-tertiary)] text-sm">
                      Select a project to view its directory structure
                    </p>
                  )}
                </div>
              )}
            </div>

            {/* Step Content */}
            <div className="rounded-xl border border-[var(--border)] bg-[var(--surface)] p-6">
              {/* Mode Toggle for Step 0/1 */}
              {(currentStep === 0 || currentStep === 1) && (
                <div className="flex items-center gap-2 mb-6 pb-4 border-b border-[var(--border)]">
                  <span className="text-sm text-[var(--foreground-secondary)]">Mode:</span>
                  <button
                    onClick={() => handleModeToggle(true)}
                    className={`px-3 py-1.5 text-sm rounded-lg transition-colors ${
                      isQuickMode
                        ? 'bg-[var(--primary)] text-white'
                        : 'bg-[var(--surface-alt)] text-[var(--foreground-secondary)] hover:bg-[var(--border)]'
                    }`}
                  >
                    Quick Mode
                  </button>
                  <button
                    onClick={() => handleModeToggle(false)}
                    className={`px-3 py-1.5 text-sm rounded-lg transition-colors ${
                      !isQuickMode
                        ? 'bg-[var(--primary)] text-white'
                        : 'bg-[var(--surface-alt)] text-[var(--foreground-secondary)] hover:bg-[var(--border)]'
                    }`}
                  >
                    Full Form
                  </button>
                </div>
              )}

              {currentStep === 0 && (
                <QuickModeStep
                  selectedProject={selectedProject}
                  quickModeText={quickModeText}
                  onTextChange={setQuickModeText}
                  onGenerate={handlePRDGenerated}
                />
              )}
              {currentStep === 1 && (
                <PRDGeneratorStep
                  prdData={prdData}
                  setPrdData={setPrdData}
                  selectedProject={selectedProject}
                  onGenerate={handlePRDGenerated}
                  generatedPRD={generatedPRD}
                />
              )}
              {currentStep === 2 && (
                <RalphConverterStep
                  prdMarkdown={generatedPRD}
                  projectName={selectedProject?.name || ''}
                  onConvert={handleRalphConverted}
                  ralphData={ralphData}
                  validationResults={validationResults}
                  setValidationResults={setValidationResults}
                  onBack={() => setCurrentStep(1)}
                />
              )}
              {currentStep === 3 && (
                <RalphyBoyStep
                  ralphData={ralphData}
                  projectPath={selectedProject?.path || ''}
                  isRunning={isRunning}
                  setIsRunning={setIsRunning}
                  currentStoryIndex={currentStoryIndex}
                  setCurrentStoryIndex={setCurrentStoryIndex}
                  logs={logs}
                  setLogs={setLogs}
                  onBack={() => setCurrentStep(2)}
                />
              )}
            </div>
          </div>
        </div>

        {/* Process Status Bar */}
        <ProcessStatusBar />

        {/* Recent Items Footer */}
        <RecentItemsFooter onItemClick={handleRecentItemClick} />
      </main>
    </SidebarDemo>
  )
}