← back to ClawCoder

src/components/onboarding/result-screen.tsx

259 lines

'use client'

import { useState, useCallback } from 'react'
import { motion } from 'framer-motion'
import Link from 'next/link'

interface GeneratedBundle {
  claudeMd: string
  rulesFiles: { path: string; content: string }[]
  settings: object
  settingsJson: string
  profile: {
    slug: string
    displayName: string
    ageGroup: string
    skillLevel: string
    persona: string
  }
}

type TabId = 'claude-md' | 'rules' | 'hooks' | 'skills' | 'mcp' | 'learning'

interface TabDef {
  id: TabId
  label: string
}

const TABS: TabDef[] = [
  { id: 'claude-md', label: 'CLAUDE.md' },
  { id: 'rules', label: 'Rules' },
  { id: 'hooks', label: 'Hooks' },
  { id: 'skills', label: 'Skills' },
  { id: 'mcp', label: 'MCP' },
  { id: 'learning', label: 'Learning Path' },
]

interface ResultScreenProps {
  bundle: GeneratedBundle
  fullProfile: {
    skillBundle: { name: string; description: string }[]
    mcpServers: {
      name: string
      package: string
      installCmd: string
      requiresKey: boolean
      trustLevel: string
    }[]
    learningPath: {
      title: string
      url: string
      type: string
      level: string
    }[]
  } | null
}

export function ResultScreen({ bundle, fullProfile }: ResultScreenProps) {
  const [activeTab, setActiveTab] = useState<TabId>('claude-md')
  const [copiedTab, setCopiedTab] = useState<string | null>(null)

  const getTabContent = useCallback(
    (tab: TabId): string => {
      switch (tab) {
        case 'claude-md':
          return bundle.claudeMd
        case 'rules':
          return bundle.rulesFiles
            .map((f) => `# ${f.path}\n\n${f.content}`)
            .join('\n\n---\n\n')
        case 'hooks':
          return bundle.settingsJson
        case 'skills':
          if (!fullProfile) return 'No skills data available.'
          return fullProfile.skillBundle
            .map((s) => `## ${s.name}\n${s.description}`)
            .join('\n\n')
        case 'mcp':
          if (!fullProfile) return 'No MCP server data available.'
          if (fullProfile.mcpServers.length === 0) return 'No MCP servers recommended for this profile.'
          return fullProfile.mcpServers
            .map(
              (m) =>
                `## ${m.name}\nPackage: ${m.package}\nInstall: ${m.installCmd}\nRequires API Key: ${m.requiresKey ? 'Yes' : 'No'}\nTrust: ${m.trustLevel}`
            )
            .join('\n\n')
        case 'learning':
          if (!fullProfile) return 'No learning path data available.'
          if (fullProfile.learningPath.length === 0) return 'No learning resources listed for this profile.'
          return fullProfile.learningPath
            .map(
              (l) =>
                `- [${l.title}](${l.url}) (${l.type}, ${l.level})`
            )
            .join('\n')
        default:
          return ''
      }
    },
    [bundle, fullProfile]
  )

  const handleCopy = useCallback(
    async (tab: TabId) => {
      const content = getTabContent(tab)
      await navigator.clipboard.writeText(content)
      setCopiedTab(tab)
      setTimeout(() => setCopiedTab(null), 2000)
    },
    [getTabContent]
  )

  const handleDownloadTab = useCallback(
    (tab: TabId) => {
      const content = getTabContent(tab)
      const filename =
        tab === 'claude-md'
          ? 'CLAUDE.md'
          : tab === 'hooks'
            ? 'settings.json'
            : `${tab}.md`
      const blob = new Blob([content], { type: 'text/plain' })
      const url = URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = filename
      a.click()
      URL.revokeObjectURL(url)
    },
    [getTabContent]
  )

  const handleDownloadAll = useCallback(() => {
    // Create a simple text bundle (no zip library dependency)
    const parts: string[] = [
      `=== CLAUDE.md ===\n\n${bundle.claudeMd}`,
      `\n\n=== settings.json ===\n\n${bundle.settingsJson}`,
    ]
    for (const rule of bundle.rulesFiles) {
      parts.push(`\n\n=== ${rule.path} ===\n\n${rule.content}`)
    }
    const blob = new Blob([parts.join('')], { type: 'text/plain' })
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `clawcoder-bundle-${bundle.profile.slug}.txt`
    a.click()
    URL.revokeObjectURL(url)
  }, [bundle])

  return (
    <div className="flex min-h-screen flex-col items-center px-4 py-12">
      <motion.div
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        transition={{ duration: 0.3 }}
        className="w-full max-w-4xl"
      >
        {/* Header */}
        <div className="mb-8 text-center">
          <h1 className="mb-2 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
            Your setup is ready!
          </h1>
          <p className="text-slate-500">
            Copy the files below into your project to get started.
          </p>
        </div>

        {/* Profile badge */}
        <div className="mb-8 flex flex-col items-center gap-2 rounded-2xl border border-green-200 bg-green-50 p-6">
          <p className="text-sm font-medium uppercase tracking-wider text-green-600">
            Generated for
          </p>
          <h2 className="text-xl font-bold text-green-900">
            {bundle.profile.displayName}
          </h2>
          <p className="max-w-md text-center text-sm text-green-700">
            {bundle.profile.persona}
          </p>
          <div className="mt-2 flex gap-3 text-xs text-green-700">
            <span className="rounded-full bg-green-100 px-3 py-1">
              {bundle.profile.ageGroup.replace('_', ' ')}
            </span>
            <span className="rounded-full bg-green-100 px-3 py-1">
              {bundle.profile.skillLevel}
            </span>
          </div>
        </div>

        {/* Tabs */}
        <div className="mb-4 flex flex-wrap gap-2 border-b border-slate-200 pb-2">
          {TABS.map((tab) => (
            <button
              key={tab.id}
              type="button"
              onClick={() => setActiveTab(tab.id)}
              className={`rounded-lg px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 ${
                activeTab === tab.id
                  ? 'bg-blue-600 text-white'
                  : 'text-slate-600 hover:bg-slate-100'
              }`}
            >
              {tab.label}
            </button>
          ))}
        </div>

        {/* Tab content */}
        <div className="relative rounded-2xl border border-slate-200 bg-slate-900">
          {/* Action buttons */}
          <div className="absolute right-3 top-3 z-10 flex gap-2">
            <button
              type="button"
              onClick={() => handleCopy(activeTab)}
              className="rounded-lg bg-slate-700 px-3 py-1.5 text-xs font-medium text-slate-200 transition-colors hover:bg-slate-600"
            >
              {copiedTab === activeTab ? 'Copied!' : 'Copy'}
            </button>
            <button
              type="button"
              onClick={() => handleDownloadTab(activeTab)}
              className="rounded-lg bg-slate-700 px-3 py-1.5 text-xs font-medium text-slate-200 transition-colors hover:bg-slate-600"
            >
              Download
            </button>
          </div>

          {/* Content */}
          <pre className="max-h-[500px] overflow-auto p-6 pt-12 text-sm leading-relaxed text-slate-200">
            <code>{getTabContent(activeTab)}</code>
          </pre>
        </div>

        {/* Bottom actions */}
        <div className="mt-8 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
          <button
            type="button"
            onClick={handleDownloadAll}
            className="min-h-[48px] rounded-xl bg-blue-600 px-8 py-3 text-base font-medium text-white transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
          >
            Download All
          </button>
          <Link
            href={`/builder?profile=${bundle.profile.slug}`}
            className="inline-flex min-h-[48px] items-center justify-center rounded-xl border border-slate-300 px-8 py-3 text-base font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
          >
            Go to Builder
          </Link>
          <Link
            href="/analyzer"
            className="inline-flex min-h-[48px] items-center justify-center rounded-xl border border-slate-300 px-8 py-3 text-base font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
          >
            Analyze This
          </Link>
        </div>
      </motion.div>
    </div>
  )
}