bookwiz.io / components / BookEditorSkeleton.tsx
BookEditorSkeleton.tsx
Raw
'use client'

import { useState, useEffect, useRef } from 'react'

const tips = [
  {
    title: "Git Version Control",
    content: "Track every change with timestamps. Commit your work to GitHub for professional backup and collaboration. Try different versions safely with branching."
  },
  {
    title: "AI Agent Capabilities",
    content: "Your AI can read, edit, and manage files directly. Ask it to 'find all mentions of the villain' or 'improve the dialogue in chapter 3' - it will search and act automatically."
  },
  {
    title: "Semantic Search",
    content: "Search by meaning, not just exact text. Find content about 'betrayal' even if the word isn't used, or discover themes across your entire book."
  },
  {
    title: "Effective AI Prompting",
    content: "Be specific: 'Help me develop the backstory for my shy protagonist who discovers magic' works better than 'write a character.' Provide context about your story and goals."
  },
  {
    title: "AI Image Generation",
    content: "Create book covers, character portraits, and scene illustrations with AI. Use detailed descriptions like 'Fantasy cover with dragons silhouetted against misty mountains.'"
  },
  {
    title: "File Organization",
    content: "Break your book into manageable pieces. Each chapter, character profile, or outline can be its own file. Use folders to organize by themes or story arcs."
  },
  {
    title: "Slash Commands",
    content: "Type '/' in the editor for quick formatting. Try /h1 for headings, /list for bullet points, /quote for blockquotes, or /table for data organization."
  },
  {
    title: "AI Writing Techniques",
    content: "Use AI for brainstorming, character interviews, plot problem-solving, and style improvements. Start with rough ideas and iterate to build on what works."
  }
]

export default function BookEditorSkeleton() {
  // Use a ref to track if we've initialized to avoid hydration issues
  const initializedRef = useRef(false)
  const [currentTipIndex, setCurrentTipIndex] = useState(0)
  const [isClient, setIsClient] = useState(false)
  const intervalRef = useRef<NodeJS.Timeout | null>(null)
  const mountedRef = useRef(false)

  // Initialize with random tip on client side
  useEffect(() => {
    if (!initializedRef.current) {
      initializedRef.current = true
      setIsClient(true)
      
      // Randomize immediately
      const randomIndex = Math.floor(Math.random() * tips.length)
      setCurrentTipIndex(randomIndex)
    }
  }, [])

  useEffect(() => {
    // Only set up interval after client has mounted and randomized the tip
    if (!isClient) return
    
    // Prevent multiple intervals if component re-renders
    if (mountedRef.current) return
    mountedRef.current = true

    // Set up interval
    intervalRef.current = setInterval(() => {
      setCurrentTipIndex((prev) => {
        const next = (prev + 1) % tips.length
        return next
      })
    }, 1500) // Change tip every 1.5 seconds

    // Cleanup on unmount
    return () => {
      mountedRef.current = false
      if (intervalRef.current) {
        clearInterval(intervalRef.current)
        intervalRef.current = null
      }
    }
  }, [isClient])

  return (
    <div className='h-screen flex flex-col md:flex-row bg-black relative overflow-hidden'>
      {/* Modern gradient background */}
      <div className="absolute inset-0 bg-gradient-to-br from-black via-gray-950 to-black pointer-events-none" />
      
      {/* Subtle animated gradient overlay */}
      <div className="absolute inset-0 opacity-30 pointer-events-none">
        <div className="absolute top-0 left-1/4 w-96 h-96 bg-gradient-to-r from-blue-600/20 to-purple-600/20 rounded-full blur-3xl animate-pulse" />
        <div className="absolute bottom-0 right-1/4 w-96 h-96 bg-gradient-to-r from-purple-600/20 to-pink-600/20 rounded-full blur-3xl animate-pulse" style={{ animationDelay: '1s' }} />
      </div>

      {/* Tips overlay */}
      <div className="absolute inset-0 flex items-center justify-center z-20 pointer-events-none">
        <div className="bg-slate-900/80 backdrop-blur-sm border border-slate-700/50 rounded-2xl p-6 max-w-md mx-4 text-center">
          <div className="text-2xl mb-3 animate-pulse"></div>
          {isClient ? (
            <>
              <h4 className="text-slate-100 font-semibold text-sm mb-2">
                {tips[currentTipIndex].title}
              </h4>
              <p className="text-slate-200 text-sm leading-relaxed">
                {tips[currentTipIndex].content}
              </p>
              <div className="flex justify-center mt-4 space-x-1">
                {tips.map((_, index) => (
                  <div
                    key={index}
                    className={`w-2 h-2 rounded-full transition-all duration-300 ${
                      index === currentTipIndex 
                        ? 'bg-teal-400' 
                        : 'bg-slate-600'
                    }`}
                  />
                ))}
              </div>
            </>
          ) : null}
        </div>
      </div>

      {/* Desktop Layout Skeleton */}
      <div className="hidden md:flex w-full relative z-10">
        {/* Left Panel - File Explorer Skeleton */}
        <div className="w-80 bg-slate-900/40 backdrop-blur-sm border-r border-slate-700/50">
          <div className="h-full flex flex-col">
            {/* Header skeleton */}
            <div className='h-12 bg-slate-800/50 border-b border-slate-700/50'>
              <div className='grid grid-cols-4 h-12'>
                {[1, 2, 3, 4].map((index) => (
                  <div
                    key={index}
                    className="flex items-center justify-center"
                  >
                    <div 
                      className="w-4 h-4 bg-slate-600/50 rounded animate-pulse"
                      style={{ animationDelay: `${index * 100}ms` }}
                    ></div>
                  </div>
                ))}
              </div>
            </div>
            
            {/* File explorer content skeleton */}
            <div className="flex-1 overflow-y-auto p-4 space-y-3">
              {/* Project name skeleton */}
              <div className="flex items-center gap-2 mb-4">
                <div className="w-4 h-4 bg-slate-600/50 rounded animate-pulse"></div>
                <div className="w-32 h-4 bg-slate-600/50 rounded animate-pulse"></div>
              </div>
              
              {/* File tree skeleton */}
              {[1, 2, 3, 4, 5, 6, 7, 8].map((index) => (
                <div
                  key={index}
                  className="flex items-center gap-2"
                  style={{ paddingLeft: `${8 + ((index - 1) % 3) * 16}px` }}
                >
                  <div 
                    className="w-3 h-3 bg-slate-600/50 rounded animate-pulse"
                    style={{ animationDelay: `${index * 150}ms` }}
                  ></div>
                  <div 
                    className="w-24 h-3 bg-slate-600/50 rounded animate-pulse"
                    style={{ animationDelay: `${index * 150 + 50}ms` }}
                  ></div>
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* Center Panel - Editor Skeleton */}
        <div className='flex-1 flex flex-col min-w-0'>
          {/* Editor header skeleton */}
          <div className='h-12 bg-slate-800/50 border-b border-slate-700/50 flex items-center px-4 justify-between'>
            <div className="flex items-center gap-3">
              <div className="w-32 h-4 bg-slate-600/50 rounded animate-pulse"></div>
              <div className="w-12 h-4 bg-slate-600/50 rounded animate-pulse"></div>
            </div>
            <div className="flex items-center gap-2">
              <div className="w-8 h-6 bg-slate-600/50 rounded animate-pulse"></div>
              <div className="w-12 h-6 bg-slate-600/50 rounded animate-pulse"></div>
            </div>
          </div>
          
          {/* Editor content skeleton */}
          <div className='flex-1 bg-slate-900/40 backdrop-blur-sm p-6'>
            <div className="space-y-4">
              {/* Editor lines skeleton */}
              {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15].map((index) => (
                <div
                  key={index}
                  className="flex items-center gap-3"
                >
                  <div 
                    className="w-8 h-3 bg-slate-600/30 rounded animate-pulse"
                    style={{ animationDelay: `${index * 100}ms` }}
                  ></div>
                  <div 
                    className={`h-3 bg-slate-600/50 rounded animate-pulse ${
                      index % 3 === 0 ? 'w-full' : 
                      index % 3 === 1 ? 'w-3/4' : 'w-1/2'
                    }`}
                    style={{ animationDelay: `${index * 100 + 50}ms` }}
                  ></div>
                </div>
              ))}
            </div>
          </div>
          
          {/* Editor footer skeleton */}
          <div className="h-8 bg-slate-800 border-t border-slate-700 flex items-center justify-between px-4">
            <div className="flex items-center gap-3">
              <div className="w-16 h-3 bg-slate-600/50 rounded animate-pulse"></div>
            </div>
            <div className="w-12 h-3 bg-slate-600/50 rounded animate-pulse"></div>
          </div>
        </div>

        {/* Right Panel - Chat Skeleton */}
        <div className="w-96 bg-slate-800/40 backdrop-blur-sm border-l border-slate-700/50">
          <div className="h-full flex flex-col">
            {/* Chat header skeleton */}
            <div className="h-12 bg-slate-800/50 border-b border-slate-700/50 flex items-center px-4">
              <div className="flex items-center gap-2">
                <div className="w-4 h-4 bg-slate-600/50 rounded animate-pulse"></div>
                <div className="w-20 h-4 bg-slate-600/50 rounded animate-pulse"></div>
              </div>
            </div>
            
            {/* Chat messages skeleton */}
            <div className="flex-1 overflow-y-auto p-4 space-y-4">
              {[1, 2, 3, 4, 5].map((index) => (
                <div
                  key={index}
                  className={`flex ${index % 2 === 0 ? 'justify-end' : 'justify-start'}`}
                >
                  <div 
                    className={`w-48 p-3 rounded-lg animate-pulse ${
                      index % 2 === 0 
                        ? 'bg-teal-600/20 border border-teal-500/30' 
                        : 'bg-slate-700/50 border border-slate-600/50'
                    }`}
                    style={{ animationDelay: `${index * 200}ms` }}
                  >
                    <div className="space-y-2">
                      <div 
                        className={`h-3 rounded ${
                          index % 2 === 0 ? 'bg-teal-400/30' : 'bg-slate-600/50'
                        }`}
                        style={{ width: `${60 + (index * 10)}%` }}
                      ></div>
                      <div 
                        className={`h-3 rounded ${
                          index % 2 === 0 ? 'bg-teal-400/30' : 'bg-slate-600/50'
                        }`}
                        style={{ width: `${40 + (index * 15)}%` }}
                      ></div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
            
            {/* Chat input skeleton */}
            <div className="p-4 border-t border-slate-700/50">
              <div className="flex items-center gap-2">
                <div className="flex-1 h-10 bg-slate-700/50 rounded-lg animate-pulse"></div>
                <div className="w-10 h-10 bg-slate-600/50 rounded-lg animate-pulse"></div>
              </div>
            </div>
          </div>
        </div>
      </div>

      {/* Mobile Layout Skeleton */}
      <div className="flex-1 flex md:hidden flex-col relative z-10 overflow-hidden">
        {/* Mobile header skeleton - matches the actual mobile header grid */}
        <div className="h-12 bg-slate-800/50 border-b border-slate-700/50">
          <div className="grid grid-cols-4 h-12">
            {[1, 2, 3, 4].map((index) => (
              <div
                key={index}
                className="flex items-center justify-center"
              >
                <div 
                  className="w-4 h-4 bg-slate-600/50 rounded animate-pulse"
                  style={{ animationDelay: `${index * 100}ms` }}
                ></div>
              </div>
            ))}
          </div>
        </div>
        
        {/* Mobile content skeleton */}
        <div className="flex-1 bg-slate-900/40 backdrop-blur-sm">
          {/* Project header skeleton - matches ExplorerTab px-4 py-3 */}
          <div className="px-4 py-3">
            <div className="flex items-center justify-between group">
              <div className="flex items-center text-sm text-slate-200 font-medium">
                <div className="w-8 h-8 rounded-xl bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center mr-3">
                  <div className="w-4 h-4 bg-slate-600/50 rounded animate-pulse"></div>
                </div>
                <div className="w-32 h-4 bg-slate-600/50 rounded animate-pulse"></div>
              </div>
              <div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-all duration-200">
                <div className="w-6 h-6 bg-slate-600/50 rounded animate-pulse"></div>
                <div className="w-6 h-6 bg-slate-600/50 rounded animate-pulse"></div>
              </div>
            </div>
          </div>
          
          {/* File explorer skeleton - matches FileExplorer px-4 py-2 */}
          <div className="px-4 py-2">
            <div className="mt-2">
              {[1, 2, 3, 4, 5, 6, 7, 8].map((index) => (
                <div
                  key={index}
                  className="flex items-center gap-2 mb-2"
                  style={{ paddingLeft: `${8 + ((index - 1) % 3) * 16}px` }}
                >
                  <div 
                    className="w-3 h-3 bg-slate-600/50 rounded animate-pulse"
                    style={{ animationDelay: `${index * 150}ms` }}
                  ></div>
                  <div 
                    className="w-24 h-3 bg-slate-600/50 rounded animate-pulse"
                    style={{ animationDelay: `${index * 150 + 50}ms` }}
                  ></div>
                </div>
              ))}
            </div>
          </div>
        </div>
        
        {/* Mobile bottom navigation skeleton */}
        <div className="h-12 bg-slate-900/95 backdrop-blur-md border-t border-slate-700/60 flex items-center justify-around px-2">
          {[1, 2, 3].map((index) => (
            <div
              key={index}
              className={`flex items-center justify-center py-2 px-4 rounded-xl transition-all duration-200 relative group ${
                index === 1 ? 'bg-teal-500/15 shadow-lg shadow-teal-500/10' : ''
              }`}
            >
              <div className="flex items-center gap-2">
                <div 
                  className={`w-4 h-4 rounded animate-pulse ${
                    index === 1 ? 'bg-teal-400/60' : 'bg-slate-600/50'
                  }`}
                  style={{ animationDelay: `${index * 100}ms` }}
                ></div>
                <div 
                  className={`w-8 h-3 rounded animate-pulse ${
                    index === 1 ? 'bg-teal-400/40' : 'bg-slate-600/50'
                  }`}
                  style={{ animationDelay: `${index * 100 + 50}ms` }}
                ></div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  )
}