export default function App()

in src/video_toys/App.tsx [42:495]


export default function App() {
  const {defaultExample, examples, setExamples, isLoading} =
    useContext(DataContext);

  const [videoUrl, setVideoUrl] = useState(
    PRESEED_CONTENT ? defaultExample?.url : '',
  );

  const [urlValidating, setUrlValidating] = useState(false); // State to track URL validation
  const [contentLoading, setContentLoading] = useState(false); // State to track content loading

  // Reference to ContentContainer component for accessing its state
  const contentContainerRef = useRef<{
    getSpec: () => string;
    getCode: () => string;
  } | null>(null);

  // Counter to force ContentContainer re-mount even if the video URL hasn't changed
  const [reloadCounter, setReloadCounter] = useState(0);
  const inputRef = useRef<HTMLInputElement>(null);
  const [selectedExample, setSelectedExample] = useState<Example | null>(
    PRESEED_CONTENT ? defaultExample : null,
  );

  // Handle 'Enter' key press in the input field
  const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === 'Enter' && !urlValidating && !contentLoading) {
      handleSubmit();
    }
  };

  const handleExampleSelect = (example: Example) => {
    if (inputRef.current) {
      inputRef.current.value = example.url;
    }
    setVideoUrl(example.url);
    setSelectedExample(example);
    setReloadCounter((c) => c + 1);
  };

  const handleSubmit = async () => {
    const inputValue = inputRef.current?.value.trim() || '';

    if (!inputValue) {
      inputRef.current?.focus();
      return;
    }

    // Prevent multiple clicks while validating
    if (urlValidating) return;

    setUrlValidating(true);
    setVideoUrl(''); // Clear previous video URL immediately
    setContentLoading(false); // Reset content loading state
    setSelectedExample(null); // Clear the selected example to force new generation

    // Check if the URL matches any of our examples
    const isPreSeededExample = [defaultExample, ...examples].some(
      (example) => example.url === inputValue,
    );

    // No need to validate the URL if it's a pre-seeded example
    if (isPreSeededExample) {
      proceedWithVideo(inputValue);
      return;
    }

    if (VALIDATE_INPUT_URL) {
      // Validate video URL
      const validationResult = await validateYoutubeUrl(inputValue);

      if (validationResult.isValid) {
        proceedWithVideo(inputValue);
      } else {
        alert(validationResult.error || 'Invalid YouTube URL');
        setUrlValidating(false);
      }
    } else {
      // If URL validation is disabled, proceed directly
      proceedWithVideo(inputValue);
    }
  };

  // Helper function to handle common operations after URL validation
  const proceedWithVideo = (url: string) => {
    setVideoUrl(url);
    // Incrementing the counter changes the 'key' prop on ContentContainer,
    // forcing it to re-mount and re-generate content
    setReloadCounter((c) => c + 1);
    setUrlValidating(false);
  };

  // Callback function to handle loading state changes from ContentContainer
  const handleContentLoadingStateChange = (isLoading: boolean) => {
    setContentLoading(isLoading);
  };

  const handleShare = async () => {
    if (!videoUrl || !contentContainerRef.current) return;

    // Get current spec and code
    const currentSpec = contentContainerRef.current.getSpec();
    const currentCode = contentContainerRef.current.getCode();

    try {
      // Set a loading state for the button
      setContentLoading(true);

      // Create share link using helper function
      const result = await createShareLink(videoUrl, currentSpec, currentCode);

      setExamples([result, ...examples]);
    } catch (err) {
      console.error('Share error:', err);
      alert('Failed to create shareable URL');
    } finally {
      setContentLoading(false);
    }
  };

  const exampleGallery = (
    <ExampleGallery
      title={PRESEED_CONTENT ? 'More examples' : 'Examples'}
      onSelectExample={handleExampleSelect}
      selectedExample={selectedExample}
    />
  );

  return (
    <>
      <main className="main-container">
        <div className="left-side">
          <h1 className="headline">Video Toys</h1>
          <p className="subtitle">
            Generate interactive learning apps from YouTube content
          </p>
          <p className="attribution">
            An experiment by <strong>Aaron Wade</strong>
          </p>
          <div className="input-container">
            <label htmlFor="youtube-url" className="input-label">
              Paste a URL from YouTube:
            </label>
            <input
              ref={inputRef}
              id="youtube-url"
              className="youtube-input"
              type="text"
              placeholder="https://www.youtube.com/watch?v=..."
              defaultValue={PRESEED_CONTENT ? defaultExample?.url : ''}
              disabled={urlValidating || contentLoading} // Disable input while validating or loading
              onKeyDown={handleKeyDown} // Add keydown handler
              onChange={() => {
                // Clear all content upon input change
                setVideoUrl('');
                setSelectedExample(null);
              }}
            />
          </div>

          <div className="button-container">
            <button
              onClick={handleSubmit}
              className="button-primary submit-button"
              disabled={urlValidating || contentLoading} // Disable button during validation or content loading
            >
              {urlValidating
                ? 'Validating URL...'
                : contentLoading
                  ? 'Generating...'
                  : 'Generate app'}
            </button>
            <button
              style={{
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
              }}
              className="button-primary share-button"
              disabled={urlValidating || contentLoading || !videoUrl} // Disable if validating, loading, or no video URL
              onClick={handleShare}>
              <span
                style={{
                  fontFamily: 'var(--font-symbols)',
                  fontSize: '1.25rem',
                }}>
                save
              </span>
            </button>
          </div>

          <div className="video-container">
            {videoUrl ? (
              <iframe
                className="video-iframe"
                src={getYoutubeEmbedUrl(videoUrl)}
                allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
                allowFullScreen></iframe>
            ) : (
              <div className="video-placeholder">Video will appear here</div>
            )}
          </div>

          <div className="gallery-container desktop-gallery-container">
            {exampleGallery}
          </div>
        </div>

        <div className="right-side">
          <div className="content-area">
            {videoUrl ? (
              <ContentContainer
                key={reloadCounter}
                contentBasis={videoUrl}
                onLoadingStateChange={handleContentLoadingStateChange}
                preSeededSpec={selectedExample?.spec}
                preSeededCode={selectedExample?.code}
                ref={contentContainerRef}
              />
            ) : (
              <div className="content-placeholder">
                <p>
                  {urlValidating
                    ? 'Validating URL...'
                    : 'Paste a YouTube URL or select an example to begin'}
                </p>
              </div>
            )}
          </div>

          <div className="gallery-container mobile-gallery-container">
            {exampleGallery}
          </div>
        </div>
      </main>

      <style>{`
        .main-container {
          --color-headline: light-dark(#000, #fff);
          --color-subtitle: light-dark(#666, #c8c8c8);
          --color-attribution: light-dark(#999, #e1e1e1);

          --color-video-container-background: light-dark(#f0f0f0, #f4f4f4);

          --color-video-placeholder-text: light-dark(#787878, #4d4d4d);

          --color-content-placeholder-border: light-dark(#ccc, #9a9b9c);
          --color-content-placeholder-text: light-dark(#787878, #f4f4f4);

          padding: 2rem;
          display: flex;
          gap: 2rem;
          height: 100vh;
          box-sizing: border-box;
          overflow: hidden;

          @media (max-width: 768px) {
            flex-direction: column;
            padding: 2.25rem 1.5rem 1.5rem;
            gap: 1rem;
            height: auto;
            overflow: visible;
          }
        }

        .left-side {
          width: 40%;
          height: 100%;
          display: flex;
          flex-direction: column;
          align-items: center;
          gap: 1rem;
          overflow-y: auto;
          scrollbar-width: none; /* Firefox */
          -ms-overflow-style: none; /* IE and Edge */

          @media (max-width: 768px) {
            width: 100%;
            height: auto;
            overflow: visible;
          }
        }

        .left-side::-webkit-scrollbar {
          display: none; /* Chrome, Safari, Opera */
        }

        .right-side {
          display: flex;
          flex-direction: column;
          flex: 1;
          gap: 1rem;
          height: 100%;

          @media (max-width: 768px) {
            height: auto;
          }
        }


        .headline {
          color: var(--color-headline);
          font-family: var(--font-display);
          font-size: 4rem;
          font-weight: 400;
          margin-top: 0.5rem;
          margin-bottom: 0;
          text-align: center;
          text-transform: uppercase;

          @media (max-width: 768px) {
            font-size: 2.625rem;
            margin-top: 0;
          }
        }

        .subtitle {
          color: var(--color-subtitle);
          font-size: 1.2rem;
          margin-top: -0.5rem;
          margin-bottom: 0;
          text-align: center;

          @media (max-width: 768px) {
            font-size: 0.875rem;
          }
        }

        .attribution {
          color: var(--color-attribution);
          font-family: var(--font-secondary);
          font-size: 0.9rem;
          font-style: italic;
          margin-bottom: 1rem;
          margin-top: -0.5rem;
          text-align: center;

          @media (max-width: 768px) {
            font-size: 0.8rem;
          }
        }

        .input-container {
          width: 100%;
        }

        .input-label {
          display: block;
          margin-bottom: 0.5rem;
        }

        .youtube-input {
          width: 100%;
        }

        .button-container {
          width: 100%;
          display: flex;
          gap: 0.5rem;
        }

        .submit-button {
          flex: 1;
        }

        .share-button {
          flex: 0.05;
        }

        .video-container {
          background-color: var(--color-video-container-background);
          border-radius: 8px;
          color: var(--color-video-placeholder-text);
          margin: 0.5rem 0;
          padding-top: 56.25%; /* 16:9 aspect ratio */
          position: relative;
          width: 100%;
        }

        .video-iframe {
          position: absolute;
          top: 0;
          left: 0;
          width: 100%;
          height: 100%;
          border: none;
          border-radius: 8px;
        }

        .video-placeholder {
          position: absolute;
          top: 0;
          left: 0;
          right: 0;
          bottom: 0;
          display: flex;
          align-items: center;
          justify-content: center;
        }

        .content-area {
          flex: 1;
          display: flex;
          flex-direction: column;
          max-height: 100%;

          @media (max-width: 768px) {
            max-height: 550px;
            min-height: 550px;
          }
        }

        .content-placeholder {
          align-items: center;
          border: 2px dashed var(--color-content-placeholder-border);
          border-radius: 8px;
          box-sizing: border-box;
          color: var(--color-content-placeholder-text);
          display: flex;
          flex-direction: column;
          font-size: 1.2rem;
          height: 100%;
          justify-content: center;
          padding: 0 2rem;
          width: 100%;

          @media (max-width: 768px) {
            min-height: inherit;
          }
        }

        .gallery-container {
          width: 100%;
        }

        .desktop-gallery-container {
          display: block;

          @media (max-width: 768px) {
            display: none; /* Hide on mobile */
          }
        }

        .mobile-gallery-container {
          display: none; /* Hide on desktop */

          @media (max-width: 768px) {
            display: block;
          }
        }
      `}</style>
    </>
  );
}