> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sampleapp.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscription Checkout (Stripe)

> Get started with our sample app to run a full, working subscription integration using Stripe Billing and Stripe Checkout

export const DownloadAndOpenButtons = ({downloadUrl, mainAppUrl = "https://sampleapp.ai", className = "", gitUrl}) => {
  const [hasCopied, setHasCopied] = React.useState(false);
  const [showCommand, setShowCommand] = React.useState(false);
  const npxCommand = React.useMemo(() => {
    let apiPath = downloadUrl;
    if (apiPath.startsWith("http://") || apiPath.startsWith("https://")) {
      try {
        const url = new URL(apiPath);
        apiPath = url.pathname;
      } catch (e) {
        console.error("Invalid URL:", apiPath);
      }
    }
    if (apiPath.startsWith("/public/")) {
      apiPath = apiPath.slice(("/public/").length);
    }
    if (apiPath.startsWith("/")) {
      apiPath = apiPath.slice(1);
    }
    if (apiPath.endsWith(".zip")) {
      apiPath = apiPath.slice(0, -4);
    }
    return `npx sampleappai add "${mainAppUrl}/api/code/static/${apiPath}"`;
  }, [downloadUrl, mainAppUrl]);
  const handleDownload = () => {
    window.open(downloadUrl, "_blank");
  };
  const handleCopyCommand = async () => {
    try {
      await navigator.clipboard.writeText(npxCommand);
      setHasCopied(true);
      setTimeout(() => setHasCopied(false), 2000);
    } catch (err) {
      const textArea = document.createElement("textarea");
      textArea.value = npxCommand;
      document.body.appendChild(textArea);
      textArea.select();
      try {
        document.execCommand("copy");
        setHasCopied(true);
        setTimeout(() => setHasCopied(false), 2000);
      } catch (err) {
        console.error("Failed to copy:", err);
      }
      document.body.removeChild(textArea);
    }
  };
  const handleOpenVSCode = () => {
    try {
      window.location.href = `vscode://vscode.git/clone?url=${gitUrl}`;
    } catch {}
  };
  return <div className={`flex flex-col gap-3 ${className}`}>
      <div className="flex items-center gap-2">
        {}
        <button type="button" className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md text-white transition-all hover:opacity-90 active:scale-[0.98] bg-primary dark:bg-primary-light" onClick={handleDownload}>
          <svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
          </svg>
          Download example
        </button>

        {}
        <button type="button" className="inline-flex items-center h-8 gap-1.5 px-3 py-0.5 text-xs font-medium rounded-md border transition-all hover:bg-zinc-100 dark:hover:bg-zinc-800 active:scale-[0.98] bg-white dark:bg-zinc-900 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-zinc-100" onClick={() => handleOpenVSCode()}>
          <img src="/assets/vscode.png" alt="VS Code" className="w-3.5 h-3.5" />
          Integrate in VS Code
        </button>
      </div>

      {}
      {showCommand && <div className="p-4 border border-zinc-300 dark:border-zinc-700 rounded-lg bg-zinc-50 dark:bg-zinc-900/50">
          <div className="flex flex-col gap-3">
            {}
            <div className="flex flex-col gap-2">
              <div className="flex items-center gap-2">
                <div className="flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold text-white" style={{
    backgroundColor: themeColor
  }}>
                  1
                </div>
                <p className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
                  Copy this command
                </p>
              </div>
              <div className="flex items-center gap-2 overflow-hidden">
                <code className="flex-1 bg-white dark:bg-zinc-800 px-3 py-2.5 rounded-md text-xs font-mono truncate block max-w-[calc(100%-3rem)] border border-zinc-200 dark:border-zinc-700 text-zinc-900 dark:text-zinc-100">
                  {npxCommand}
                </code>
                <button type="button" onClick={handleCopyCommand} className="flex-shrink-0 p-2 rounded-md hover:bg-zinc-200 dark:hover:bg-zinc-700 transition-colors border border-zinc-300 dark:border-zinc-600" title="Copy command">
                  {hasCopied ? <svg className="w-4 h-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
                    </svg> : <svg className="w-4 h-4 text-zinc-600 dark:text-zinc-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                      <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
                    </svg>}
                </button>
              </div>
            </div>

            {}
            <div className="flex flex-col gap-2">
              <div className="flex items-center gap-2">
                <div className="flex-shrink-0 w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold text-white" style={{
    backgroundColor: themeColor
  }}>
                  2
                </div>
                <p className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
                  Open VSCode and paste in terminal
                </p>
              </div>
              <button type="button" onClick={handleOpenVSCode} className="w-full inline-flex items-center justify-center gap-2 px-4 py-2.5 text-sm font-medium rounded-md text-white transition-all hover:opacity-90 active:scale-[0.99]" style={{
    backgroundColor: themeColor
  }}>
                <svg className="w-4 h-4" width="20" height="20" viewBox="0 0 20 20" fill="none">
                  <path d="M13.3333 0L5 3.33333V16.6667L13.3333 20V0Z" fill="currentColor" />
                  <path d="M13.3333 0L18.3333 3.75V16.25L13.3333 20V0Z" fill="currentColor" opacity="0.8" />
                </svg>
                Open VSCode
              </button>
            </div>
          </div>
        </div>}
    </div>;
};

export const FrameworkGuide = ({framework, label, children}) => {
  return children;
};

export const Architecture = ({value, label, children}) => {
  return children;
};

export const iframeState = {
  listeners: [],
  params: {},
  subscribe: fn => {
    iframeState.listeners.push(fn);
    return () => {
      iframeState.listeners = iframeState.listeners.filter(l => l !== fn);
    };
  },
  setParams: params => {
    iframeState.params = params;
    iframeState.listeners.forEach(fn => fn(params));
  }
};

export const CodeFocusSection = ({docs = "mintlify", themeColor, linesStart, linesEnd, activeFilePath, title, description, children}) => {
  const sectionRef = React.useRef(null);
  const hasTriggeredRef = React.useRef(false);
  const [isActive, setIsActive] = React.useState(false);
  const useProse = docs === "mintlify";
  const getParams = React.useCallback(() => {
    const params = {};
    if (docs) params.docs = docs;
    if (docs === "custom" && themeColor) params.theme = themeColor;
    if (linesStart !== undefined) params.linesStart = linesStart;
    if (linesEnd !== undefined) params.linesEnd = linesEnd;
    if (activeFilePath) params.activeFilePath = activeFilePath;
    return params;
  }, [docs, themeColor, linesStart, linesEnd, activeFilePath]);
  const handleFocus = React.useCallback(() => {
    iframeState.setParams(getParams());
    setIsActive(true);
  }, [getParams]);
  React.useEffect(() => {
    const checkActive = params => {
      const myParams = getParams();
      const isMatch = params.activeFilePath === myParams.activeFilePath && params.linesStart === myParams.linesStart && params.linesEnd === myParams.linesEnd;
      setIsActive(isMatch);
      if (params.platform !== undefined || params.framework !== undefined || params.activeFilePath !== myParams.activeFilePath) {
        hasTriggeredRef.current = false;
      }
    };
    checkActive(iframeState.params);
    return iframeState.subscribe(checkActive);
  }, [getParams]);
  React.useEffect(() => {
    const element = sectionRef.current;
    if (!element) return;
    const observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting && !hasTriggeredRef.current) {
          hasTriggeredRef.current = true;
          handleFocus();
        } else if (!entry.isIntersecting) {
          hasTriggeredRef.current = false;
        }
      });
    }, {
      threshold: 0,
      rootMargin: "-40% 0px -40% 0px"
    });
    observer.observe(element);
    return () => observer.disconnect();
  }, [handleFocus]);
  return <div ref={sectionRef} onClick={handleFocus} className="relative py-6 transition-colors cursor-pointer" style={{
    marginLeft: "-1.5rem",
    marginRight: "-1.5rem",
    paddingLeft: "1.5rem",
    paddingRight: "1.5rem"
  }}>
      <div className="absolute top-0 h-px" style={{
    backgroundColor: "rgba(128,128,128,0.2)",
    left: "-2rem",
    right: "-2rem"
  }} />
      {isActive && <div className={`absolute -left-2 top-0 bottom-0 w-1 ${useProse ? "bg-primary dark:bg-primary-light" : ""}`} style={!useProse && themeColor ? {
    backgroundColor: themeColor
  } : undefined} />}
      {title && <div className="flex items-center gap-2 mb-2">
          <h4 className={`text-base font-semibold m-0 ${useProse && isActive ? "text-primary dark:text-primary-light" : ""}`} style={!useProse && isActive && themeColor ? {
    color: themeColor
  } : undefined}>
            {title}
          </h4>
        </div>}
      {description && <p className="text-sm text-gray-500 dark:text-gray-400 mt-1 mb-0">
          {description}
        </p>}
      {children && <div className="mt-3">{children}</div>}
    </div>;
};

export const SideBySideLayout = ({previewSrc, children}) => {
  const iframeRef = React.useRef(null);
  const pendingParams = React.useRef(null);
  const iframeReady = React.useRef(false);
  const baseUrlRef = React.useRef(previewSrc);
  const architectures = React.useMemo(() => {
    const derived = [];
    React.Children.forEach(children, child => {
      if (child?.type === Architecture) {
        const archValue = child.props.value;
        const archLabel = child.props.label;
        const frameworks = [];
        React.Children.forEach(child.props.children, frameworkChild => {
          if (frameworkChild?.type === FrameworkGuide) {
            frameworks.push({
              value: frameworkChild.props.framework,
              label: frameworkChild.props.label || frameworkChild.props.framework
            });
          }
        });
        derived.push({
          value: archValue,
          label: archLabel,
          frameworks
        });
      }
    });
    return derived;
  }, [children]);
  const SettingsNavInternal = () => {
    const parseFrameworkValue = (archValue, fwValue, fwLabel) => {
      const archPrefix = archValue.toLowerCase().split(" ")[0];
      const archPrefixMap = {
        javascript: "js",
        ios: "ios",
        android: "ad"
      };
      const prefix = archPrefixMap[archPrefix] || archPrefix;
      let remaining = fwValue;
      if (fwValue.startsWith(prefix + "-")) {
        remaining = fwValue.slice(prefix.length + 1);
      }
      const labelParts = fwLabel.split(" + ");
      if (!fwLabel.includes(" + ")) {
        return {
          frontend: remaining,
          frontendLabel: fwLabel,
          backend: null,
          backendLabel: null,
          isFullStack: true
        };
      }
      const parts = remaining.split("-");
      if (parts.length >= 2) {
        return {
          frontend: parts[0],
          frontendLabel: labelParts[0] || parts[0],
          backend: parts.slice(1).join("-"),
          backendLabel: labelParts[1] || parts.slice(1).join("-"),
          isFullStack: false
        };
      }
      return {
        frontend: remaining,
        frontendLabel: fwLabel,
        backend: null,
        backendLabel: null,
        isFullStack: true
      };
    };
    const [selections, setSelections] = React.useState(() => {
      const defaults = {};
      if (architectures && architectures.length > 0) {
        const firstArch = architectures[0];
        defaults.platform = firstArch.value;
        if (firstArch.frameworks && firstArch.frameworks.length > 0) {
          const firstFw = firstArch.frameworks[0];
          const parsed = parseFrameworkValue(firstArch.value, firstFw.value, firstFw.label);
          defaults.client = parsed.frontend;
          defaults.server = parsed.backend;
          defaults.framework = firstFw.value;
        }
      }
      return defaults;
    });
    const getAvailableOptions = React.useMemo(() => {
      const selectedArch = architectures?.find(arch => arch.value === selections.platform);
      const frameworks = selectedArch?.frameworks || [];
      const frontends = new Map();
      const backends = new Map();
      const fullStackFrameworks = [];
      frameworks.forEach(fw => {
        const parsed = parseFrameworkValue(selectedArch?.value || "", fw.value, fw.label);
        if (parsed.isFullStack) {
          fullStackFrameworks.push({
            value: parsed.frontend,
            label: parsed.frontendLabel,
            fullFrameworkValue: fw.value
          });
        } else {
          if (!frontends.has(parsed.frontend)) {
            frontends.set(parsed.frontend, {
              value: parsed.frontend,
              label: parsed.frontendLabel
            });
          }
          if (parsed.backend && !backends.has(parsed.backend)) {
            backends.set(parsed.backend, {
              value: parsed.backend,
              label: parsed.backendLabel
            });
          }
        }
      });
      fullStackFrameworks.forEach(fs => {
        if (!frontends.has(fs.value)) {
          frontends.set(fs.value, {
            value: fs.value,
            label: fs.label,
            isFullStack: true,
            fullFrameworkValue: fs.fullFrameworkValue
          });
        }
      });
      return {
        frontends: Array.from(frontends.values()),
        backends: Array.from(backends.values()),
        fullStackFrameworks
      };
    }, [architectures, selections.platform]);
    const isFullStackSelected = React.useMemo(() => {
      const frontend = getAvailableOptions.frontends.find(f => f.value === selections.client);
      return frontend?.isFullStack || false;
    }, [getAvailableOptions.frontends, selections.client]);
    const sections = [{
      id: "platform",
      label: "Architecture:",
      options: architectures || []
    }, {
      id: "client",
      label: "Frontend:",
      options: getAvailableOptions.frontends
    }, ...isFullStackSelected ? [] : [{
      id: "server",
      label: "Backend:",
      options: getAvailableOptions.backends
    }]];
    React.useEffect(() => {
      if (selections.platform || selections.framework) {
        iframeState.setParams({
          ...iframeState.params,
          ...selections
        });
      }
    }, []);
    const handleSelect = (sectionId, value) => {
      let newSelections = {
        ...selections,
        [sectionId]: value
      };
      const getArchPrefix = archValue => {
        const archPrefix = archValue.toLowerCase().split(" ")[0];
        const archPrefixMap = {
          javascript: "js",
          ios: "ios",
          android: "ad"
        };
        return archPrefixMap[archPrefix] || archPrefix;
      };
      const getBackendsForArch = archValue => {
        const arch = architectures?.find(a => a.value === archValue);
        const backends = new Map();
        arch?.frameworks?.forEach(fw => {
          const parsed = parseFrameworkValue(arch.value, fw.value, fw.label);
          if (!parsed.isFullStack && parsed.backend) {
            backends.set(parsed.backend, parsed.backendLabel);
          }
        });
        return Array.from(backends.keys());
      };
      if (sectionId === "platform") {
        const newArch = architectures?.find(arch => arch.value === value);
        if (newArch?.frameworks && newArch.frameworks.length > 0) {
          const firstFw = newArch.frameworks[0];
          const parsed = parseFrameworkValue(newArch.value, firstFw.value, firstFw.label);
          newSelections.client = parsed.frontend;
          newSelections.server = parsed.backend;
          newSelections.framework = firstFw.value;
        }
      }
      if (sectionId === "client") {
        const selectedArch = architectures?.find(arch => arch.value === newSelections.platform);
        const frameworks = selectedArch?.frameworks || [];
        const fullStackFw = frameworks.find(fw => {
          const parsed = parseFrameworkValue(selectedArch?.value || "", fw.value, fw.label);
          return parsed.isFullStack && parsed.frontend === value;
        });
        if (fullStackFw) {
          newSelections.server = null;
          newSelections.framework = fullStackFw.value;
        } else {
          const prefix = getArchPrefix(newSelections.platform);
          let server = newSelections.server;
          if (!server) {
            const availableBackends = getBackendsForArch(newSelections.platform);
            server = availableBackends[0] || null;
            newSelections.server = server;
          }
          if (server) {
            newSelections.framework = `${prefix}-${value}-${server}`;
          }
        }
      }
      if (sectionId === "server") {
        const prefix = getArchPrefix(newSelections.platform);
        const client = newSelections.client;
        newSelections.framework = `${prefix}-${client}-${value}`;
      }
      setSelections(newSelections);
      iframeState.setParams({
        ...iframeState.params,
        ...newSelections
      });
    };
    return <div className="w-full sticky top-0 z-50 lg:-mx-8 px-4 lg:px-8 py-6 backdrop-blur-md">
        <div className="flex flex-wrap items-center justify-between">
          <div className="flex flex-wrap items-center" style={{
      marginLeft: "-24px",
      marginTop: "-24px"
    }}>
            {sections.map(section => <div key={section.id} className="flex items-center flex-wrap" style={{
      marginTop: "24px",
      marginLeft: "24px"
    }}>
                <span className="text-sm font-semibold text-gray-700 dark:text-gray-300 whitespace-nowrap mr-3">
                  {section.label}
                </span>

                {}
                <fieldset className="hidden 2xl:block border-0 p-0 m-0">
                  <legend className="sr-only">{section.label}</legend>
                  <div className="flex items-center gap-2">
                    {section.options.map(option => {
      const isSelected = selections[section.id] === option.value;
      return <div key={option.value} className="relative">
                          <input type="radio" id={`${section.id}-${option.value}`} name={section.id} value={option.value} checked={isSelected} onChange={() => handleSelect(section.id, option.value)} className="sr-only" />
                          <label htmlFor={`${section.id}-${option.value}`} className={`inline-flex items-center px-3 py-1.5 text-xs font-medium rounded-md border transition-all cursor-pointer active:scale-[0.98] ${isSelected ? "bg-white dark:bg-zinc-900 border-primary dark:border-primary-light text-primary dark:text-primary-light hover:bg-zinc-100 dark:hover:bg-zinc-800" : "bg-white dark:bg-zinc-900 border-zinc-300 dark:border-zinc-700 text-zinc-900 dark:text-zinc-100 hover:bg-zinc-100 dark:hover:bg-zinc-800"}`}>
                            {option.label}
                          </label>
                        </div>;
    })}
                  </div>
                </fieldset>

                {}
                <div className="block 2xl:hidden relative">
                  <select value={selections[section.id]} onChange={e => handleSelect(section.id, e.target.value)} className="appearance-none bg-white dark:bg-zinc-900 border border-zinc-300 dark:border-zinc-700 rounded-md px-3 py-1.5 pr-8 text-xs font-medium text-zinc-900 dark:text-zinc-100 focus:outline-none focus:ring-2 focus:ring-primary dark:focus:ring-primary-light focus:border-transparent transition-all hover:bg-zinc-100 dark:hover:bg-zinc-800">
                    {section.options.map(option => <option key={option.value} value={option.value}>
                        {option.label}
                      </option>)}
                  </select>
                  <svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" className="absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-zinc-400">
                    <path fillRule="evenodd" clipRule="evenodd" d="M2.35 9.915a.875.875 0 0 1 1.235-.065L8 13.823l4.415-3.973a.875.875 0 0 1 1.17 1.3l-5 4.5a.873.873 0 0 1-1.17 0l-5-4.5a.875.875 0 0 1-.065-1.235ZM7.415.35a.873.873 0 0 1 1.17 0l5 4.5a.875.875 0 1 1-1.17 1.3L8 2.177 3.585 6.15a.875.875 0 0 1-1.17-1.3l5-4.5Z" fill="currentColor" />
                  </svg>
                </div>
              </div>)}
          </div>
        </div>
      </div>;
  };
  const getCurrentColor = () => {
    const temp = document.createElement("div");
    temp.className = document.documentElement.classList.contains("dark") ? "dark:bg-primary-light" : "bg-primary";
    document.body.appendChild(temp);
    const bgColor = getComputedStyle(temp).backgroundColor;
    document.body.removeChild(temp);
    return bgColor;
  };
  function rgbToHex(rgb) {
    const match = rgb.match(/\d+/g);
    if (!match) return null;
    const [r, g, b] = match.map(Number);
    return "#" + [r, g, b].map(x => x.toString(16).padStart(2, "0")).join("");
  }
  const getCurrentColorInHexadecimal = () => {
    return "%23" + rgbToHex(getCurrentColor()).split("#")[1];
  };
  const updateIframeTheme = React.useCallback(() => {
    if (iframeRef.current) {
      const currentParams = iframeState.params;
      let newSrc = baseUrlRef.current + "&theme=" + getCurrentColorInHexadecimal();
      if (currentParams.iframeUrl) {
        newSrc += "&iframeUrl=" + encodeURIComponent(currentParams.iframeUrl);
      }
      iframeRef.current.src = newSrc;
    }
  }, []);
  React.useEffect(() => {
    const observer = new MutationObserver(mutations => {
      mutations.forEach(mutation => {
        if (mutation.attributeName === "class") {
          updateIframeTheme();
        }
      });
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => observer.disconnect();
  }, [updateIframeTheme]);
  React.useEffect(() => {
    let lastFramework = iframeState.params.framework;
    let lastFilePath = iframeState.params.activeFilePath;
    let debounceTimer = null;
    const handleParamsChange = params => {
      if (params.framework && params.framework !== lastFramework) {
        lastFramework = params.framework;
        if (iframeRef.current) {
          const url = new URL(iframeRef.current.src);
          url.searchParams.set("framework", params.framework);
          iframeRef.current.src = url.toString();
          return;
        }
      }
      if (debounceTimer) {
        clearTimeout(debounceTimer);
      }
      const fileChanged = params.activeFilePath && params.activeFilePath !== lastFilePath;
      if (fileChanged) {
        lastFilePath = params.activeFilePath;
      }
      const delay = fileChanged ? 150 : 0;
      debounceTimer = setTimeout(() => {
        const message = {
          type: "UPDATE_VIEW",
          ...params
        };
        if (iframeRef.current?.contentWindow) {
          iframeRef.current.contentWindow.postMessage(message, "*");
        }
      }, delay);
    };
    const unsubscribe = iframeState.subscribe(handleParamsChange);
    return () => {
      if (debounceTimer) clearTimeout(debounceTimer);
      unsubscribe();
    };
  }, []);
  const ActiveContent = () => {
    const [currentState, setCurrentState] = React.useState(() => {
      const params = iframeState.params;
      return {
        platform: params.platform || architectures[0]?.value,
        framework: params.framework || architectures[0]?.frameworks[0]?.value
      };
    });
    const [currentPageId, setCurrentPageId] = React.useState("1");
    React.useEffect(() => {
      const handleUpdate = params => {
        if (params.platform !== undefined || params.framework !== undefined) {
          setCurrentState(prev => ({
            platform: params.platform ?? prev.platform,
            framework: params.framework ?? prev.framework
          }));
        }
      };
      return iframeState.subscribe(handleUpdate);
    }, []);
    let matchedContent = null;
    React.Children.forEach(children, child => {
      if (child?.type === Architecture && child.props.value === currentState.platform) {
        React.Children.forEach(child.props.children, frameworkChild => {
          if (frameworkChild?.type === FrameworkGuide && frameworkChild.props.framework === currentState.framework) {
            matchedContent = frameworkChild.props.children;
          }
        });
      }
    });
    const isPageComponent = child => {
      if (child?.props?.["data-page-id"]) return true;
      if (child?.type?.name === "Page") return true;
      if (typeof child?.type === "function" && child?.props?.id && child?.props?.title) {
        return true;
      }
      return false;
    };
    const pages = React.useMemo(() => {
      const extracted = [];
      React.Children.forEach(matchedContent, child => {
        if (isPageComponent(child)) {
          let verificationSteps = null;
          const stepsData = child.props["data-page-verification-steps"] || child.props.verificationSteps;
          if (stepsData) {
            try {
              verificationSteps = typeof stepsData === "string" ? JSON.parse(stepsData) : stepsData;
            } catch (e) {
              console.warn("Failed to parse verification steps:", e);
            }
          }
          extracted.push({
            id: child.props["data-page-id"] || child.props.id,
            title: child.props["data-page-title"] || child.props.title,
            content: child.props.children,
            verificationSteps
          });
        }
      });
      return extracted;
    }, [matchedContent]);
    React.useEffect(() => {
      if (pages.length > 0 && !pages.find(p => p.id === currentPageId)) {
        setCurrentPageId(pages[0].id);
      }
    }, [pages, currentPageId]);
    if (pages.length === 0) {
      return <>{matchedContent}</>;
    }
    if (pages.length === 1) {
      const currentPage = pages[0];
      return <>{currentPage?.content}</>;
    }
    const currentPageIndex = pages.findIndex(p => p.id === currentPageId);
    const currentPage = pages[currentPageIndex];
    const nextPage = currentPageIndex < pages.length - 1 ? pages[currentPageIndex + 1] : null;
    const [showStepsPopup, setShowStepsPopup] = React.useState(false);
    const [showVerificationPopup, setShowVerificationPopup] = React.useState(false);
    const [verificationSteps, setVerificationSteps] = React.useState([]);
    const [currentVerificationIndex, setCurrentVerificationIndex] = React.useState(0);
    const [verificationComplete, setVerificationComplete] = React.useState(false);
    const getVerificationStepsForPage = pageId => {
      const page = pages.find(p => p.id === pageId);
      if (page?.verificationSteps && page.verificationSteps.length > 0) {
        return page.verificationSteps;
      }
      const defaultSteps = [{
        id: 1,
        label: "Checking configuration"
      }, {
        id: 2,
        label: "Validating setup"
      }, {
        id: 3,
        label: "Verifying connection"
      }, {
        id: 4,
        label: "Ready to continue"
      }];
      return defaultSteps;
    };
    const goToPage = pageId => {
      setCurrentPageId(pageId);
      setShowStepsPopup(false);
      window.scrollTo({
        top: 0,
        behavior: "smooth"
      });
    };
    const handleNextClick = () => {
      if (!nextPage) return;
      const steps = getVerificationStepsForPage(currentPage.id);
      setVerificationSteps(steps);
      setCurrentVerificationIndex(0);
      setVerificationComplete(false);
      setShowVerificationPopup(true);
      steps.forEach((_, index) => {
        setTimeout(() => {
          setCurrentVerificationIndex(index);
          if (index === steps.length - 1) {
            setTimeout(() => {
              setVerificationComplete(true);
            }, 600);
          }
        }, (index + 1) * 800);
      });
    };
    const handleVerificationComplete = () => {
      setShowVerificationPopup(false);
      if (nextPage) {
        goToPage(nextPage.id);
      }
    };
    const barContainerRef = React.useRef(null);
    React.useEffect(() => {
      const handleClickOutside = event => {
        if (barContainerRef.current && !barContainerRef.current.contains(event.target) && (showStepsPopup || showVerificationPopup)) {
          if (showVerificationPopup && !verificationComplete) {
            return;
          }
          setShowStepsPopup(false);
          if (verificationComplete) {
            setShowVerificationPopup(false);
          }
        }
      };
      if (showStepsPopup || showVerificationPopup) {
        document.addEventListener("mousedown", handleClickOutside);
        return () => {
          document.removeEventListener("mousedown", handleClickOutside);
        };
      }
    }, [showStepsPopup, showVerificationPopup, verificationComplete]);
    return <div className="page-container relative">
        {}
        <div className="page-content pb-24">{currentPage?.content}</div>

        {}
        <div className="sticky bottom-4 z-40" ref={barContainerRef}>
          <div className="relative">
            {}
            <div className="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-700 shadow-lg rounded-xl px-4 py-3">
              <div className="flex items-center justify-between gap-4">
                {}
                <button onClick={() => setShowStepsPopup(!showStepsPopup)} className="flex items-center gap-3 px-3 py-2 rounded-lg bg-gray-100 dark:bg-zinc-800 hover:bg-gray-200 dark:hover:bg-zinc-700 transition-all">
                  <div className="flex items-center gap-2">
                    <div className="w-6 h-6 rounded-full bg-primary dark:bg-primary-light text-white text-xs font-bold flex items-center justify-center">
                      {currentPageIndex + 1}
                    </div>
                    <span className="text-sm font-medium text-gray-700 dark:text-gray-300">
                      {currentPage?.title}
                    </span>
                  </div>
                  <svg className={`w-4 h-4 text-gray-400 transition-transform ${showStepsPopup ? "rotate-180" : ""}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                    <path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
                  </svg>
                </button>

                {}
                {nextPage ? <button onClick={handleNextClick} className="flex items-center gap-2 px-5 py-2 rounded-lg bg-primary dark:bg-primary-light text-white font-medium hover:opacity-90 transition-all">
                    <span>Verify</span>
                    <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                      <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
                    </svg>
                  </button> : <div className="flex items-center gap-2 px-5 py-2 rounded-lg bg-green-500 text-white font-medium">
                    <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                      <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                    </svg>
                    <span>Complete</span>
                  </div>}
              </div>
            </div>

            {}
            {showStepsPopup && <div className="absolute bottom-full left-0 right-0 mb-2 z-50">
                <div className="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-700 shadow-2xl rounded-xl overflow-hidden animate-expand-up">
                  {}
                  <div className="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-zinc-700 bg-gray-50 dark:bg-zinc-800/50">
                    <div className="flex items-center gap-2">
                      <div className="w-5 h-5 rounded-full bg-primary dark:bg-primary-light text-white text-xs font-bold flex items-center justify-center leading-none">
                        {pages.length}
                      </div>
                      <span className="text-sm font-semibold text-gray-900 dark:text-white leading-none">
                        Steps
                      </span>
                    </div>
                    <button onClick={() => setShowStepsPopup(false)} className="p-1 rounded-md hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors flex-shrink-0">
                      <svg className="w-4 h-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                        <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
                      </svg>
                    </button>
                  </div>

                  {}
                  <div className="p-3 space-y-1 max-h-80 overflow-y-auto">
                    {pages.map((page, index) => {
      const isCompleted = index < currentPageIndex;
      const isCurrent = page.id === currentPageId;
      const isDisabled = index > currentPageIndex;
      return <div key={page.id} className="relative group">
                          <button onClick={() => !isDisabled && goToPage(page.id)} disabled={isDisabled} className={`w-full flex items-center gap-3 p-2.5 rounded-lg text-left transition-all ${isCurrent ? "bg-primary/10 dark:bg-primary-light/10 border border-primary/30 dark:border-primary-light/30" : isDisabled ? "opacity-50 cursor-not-allowed" : "hover:bg-gray-100 dark:hover:bg-zinc-800"}`}>
                            {}
                            <div className={`flex-shrink-0 w-7 h-7 rounded-full flex items-center justify-center text-xs font-bold transition-all ${isCompleted ? "bg-primary dark:bg-primary-light text-white" : isCurrent ? "bg-primary dark:bg-primary-light text-white" : isDisabled ? "bg-gray-100 dark:bg-zinc-800 text-gray-400 dark:text-gray-600" : "bg-gray-200 dark:bg-zinc-700 text-gray-500 dark:text-gray-400"}`}>
                              {isCompleted ? <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
                                  <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                                </svg> : index + 1}
                            </div>
                            {}
                            <span className={`text-sm font-medium ${isCurrent ? "text-primary dark:text-primary-light" : isCompleted ? "text-gray-500 dark:text-gray-400" : isDisabled ? "text-gray-400 dark:text-gray-600" : "text-gray-700 dark:text-gray-300"}`}>
                              {page.title}
                            </span>
                          </button>
                          {}
                          {isDisabled && <div className="absolute left-0 bottom-full mb-2 w-64 px-3 py-2 bg-gray-900 dark:bg-gray-700 text-white text-xs rounded-lg shadow-lg opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50">
                              Please complete all steps in this guide and press
                              verify
                              <div className="absolute top-full left-4 w-0 h-0 border-l-4 border-r-4 border-t-4 border-transparent border-t-gray-900 dark:border-t-gray-700"></div>
                            </div>}
                        </div>;
    })}
                  </div>
                </div>
              </div>}

            {}
            {showVerificationPopup && <div className="absolute bottom-full left-0 right-0 mb-2 z-50">
                <div className="bg-white dark:bg-zinc-900 border border-gray-200 dark:border-zinc-700 shadow-2xl rounded-xl overflow-hidden animate-expand-up">
                  {}
                  <div className="flex items-center justify-between px-3 py-2 border-b border-gray-200 dark:border-zinc-700 bg-gray-50 dark:bg-zinc-800/50">
                    <div className="flex items-center gap-2">
                      <div className="w-5 h-5 rounded-full bg-primary dark:bg-primary-light text-white text-xs font-bold flex items-center justify-center leading-none">
                        {verificationSteps.length}
                      </div>
                      <span className="text-sm font-semibold text-gray-900 dark:text-white leading-none">
                        {verificationComplete ? "Verification Complete" : "Verifying..."}
                      </span>
                    </div>
                    {verificationComplete && <button onClick={() => setShowVerificationPopup(false)} className="p-1 rounded-md hover:bg-gray-200 dark:hover:bg-zinc-700 transition-colors flex-shrink-0">
                        <svg className="w-4 h-4 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                          <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>
                      </button>}
                  </div>

                  {}
                  <div className="p-3 space-y-2">
                    {verificationSteps.map((step, index) => {
      const isChecked = index <= currentVerificationIndex;
      const isActive = index === currentVerificationIndex && !verificationComplete;
      return <div key={step.id} className={`flex items-center gap-3 p-2 rounded-lg transition-all duration-300 ${isActive ? "bg-primary/5 dark:bg-primary-light/5" : ""}`}>
                          {}
                          <div className={`flex-shrink-0 w-6 h-6 rounded-md border-2 flex items-center justify-center transition-all duration-300 ${isChecked ? "bg-primary dark:bg-primary-light border-primary dark:border-primary-light" : "border-gray-300 dark:border-zinc-600"}`}>
                            {isChecked && <svg className="w-4 h-4 text-white animate-check" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={3}>
                                <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                              </svg>}
                          </div>
                          {}
                          <span className={`text-sm font-medium transition-all duration-300 ${isChecked ? "text-gray-900 dark:text-white" : "text-gray-400 dark:text-gray-500"}`}>
                            {step.label}
                          </span>
                          {}
                          {isActive && <div className="ml-auto">
                              <svg className="w-4 h-4 text-primary dark:text-primary-light 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 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                              </svg>
                            </div>}
                        </div>;
    })}
                  </div>

                  {}
                  {verificationComplete && <div className="px-3 pb-3">
                      <button onClick={handleVerificationComplete} className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg bg-primary dark:bg-primary-light text-white font-medium hover:opacity-90 transition-all text-sm">
                        <span>Continue to {nextPage?.title}</span>
                        <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
                          <path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
                        </svg>
                      </button>
                    </div>}
                </div>
              </div>}
          </div>
        </div>

        {}
        <style>{`
          @keyframes expand-up {
            from {
              transform: translateY(8px) scale(0.98);
              opacity: 0;
            }
            to {
              transform: translateY(0) scale(1);
              opacity: 1;
            }
          }
          .animate-expand-up {
            animation: expand-up 0.2s ease-out;
          }
          @keyframes check {
            from {
              transform: scale(0);
            }
            to {
              transform: scale(1);
            }
          }
          .animate-check {
            animation: check 0.2s ease-out;
          }
        `}</style>
      </div>;
  };
  return <div className="mx-auto px-4 lg:px-8 flex flex-col">
      <SettingsNavInternal />

      <div className="rounded-2xl shadow-sm flex-1">
        <div className="grid gap-8 lg:gap-10 items-start" style={{
    gridTemplateColumns: "minmax(0, 0.8fr) minmax(0, 1.2fr)"
  }}>
          <div className="prose prose-gray dark:prose-invert max-w-none pr-2">
            <ActiveContent />
          </div>
          <div className="sticky top-20">
            <div style={{
    borderRadius: "0.5rem",
    overflow: "hidden",
    background: "#020617",
    height: "calc(100vh - 180px)"
  }}>
              <iframe ref={iframeRef} src={(() => {
    const src = previewSrc + "&theme=" + getCurrentColorInHexadecimal() + (iframeState.params.iframeUrl ? "&iframeUrl=" + encodeURIComponent(iframeState.params.iframeUrl) : "");
    return src;
  })()} title="App preview" style={{
    width: "100%",
    height: "calc(100vh - 180px)",
    border: "0"
  }} allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowFullScreen />
            </div>
          </div>
        </div>
      </div>
    </div>;
};

<SideBySideLayout previewSrc="https://stripe-sandbox.sampleapp.ai/prebuilt-subscription-checkout?framework=express&isFrame=true">
  <Architecture value="JavaScript SDK" label="JavaScript SDK">
    <FrameworkGuide framework="react-node-js" label="React + Node.js">
      # Prebuilt subscription page with Stripe Checkout

      Get started with our sample app to run a full, working subscription integration using Stripe Billing and Stripe Checkout.

      The sample app demonstrates redirecting your customers from your site to a pre-built payment page hosted on Stripe. The Stripe Billing APIs create and manage subscriptions, invoices, and recurring payments, while Checkout provides the pre-built, secure, Stripe-hosted UI for collecting payment details.

      Click each step to see the corresponding sample code. As you interact with the steps, such as adding pricing data, the builder updates the sample code.

      Download and customise the sample app locally to test your integration.

      <DownloadAndOpenButtons downloadUrl="https://github.com/sampleapp-ai/stripe-checkout-prebuilt-subscription/archive/refs/heads/main.zip" gitUrl="https://github.com/sampleapp-ai/stripe-checkout-prebuilt-subscription" className="mt-4 mb-6" />

      ### ❶ Set up products, pricing, and payment methods

      <CodeFocusSection activeFilePath="src/App.jsx" linesStart={9} linesEnd={10} title="Add your products and prices">
        Create new Products and Prices that you can use in this sample.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/App.jsx" linesStart={9} linesEnd={10} title="Add features to your product">
        Create features, such as an annual birthday gift, and associate them with your subscription to entitle new subscribers to them. Listen to the active entitlements summary events for your event destination, and use the list active entitlements API for a given customer to fulfil your customer's entitlements.

        **Note:** Create or select a product before adding a feature.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/App.jsx" linesStart={9} linesEnd={10} title="(Optional) Enable payment methods">
        Use your Dashboard to enable supported payment methods that you want to accept in addition to cards. Checkout dynamically displays your enabled payment methods in order of relevance, based on the customer's location and other characteristics.
      </CodeFocusSection>

      ### ❷ Build your subscription page

      <CodeFocusSection activeFilePath="src/App.jsx" linesStart={6} linesEnd={19} title="Add a pricing preview page">
        Add a page to your site that displays your product and allows your customers to subscribe to it. Clicking Checkout redirects them to a Stripe-hosted Checkout page, which finalises the order and prevents further modification.

        Consider embedding a [pricing table](https://stripe.com/docs/payments/checkout/pricing-table) to dynamically display your pricing information through the Dashboard. Clicking a pricing option redirects your customer to the payment page.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/App.jsx" linesStart={13} linesEnd={19} title="Add a checkout button">
        The button on your order preview page redirects your customer to the Stripe-hosted payment page and uses your product's `lookup_key` to retrieve the `price_id` from the server.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/App.jsx" linesStart={25} linesEnd={43} title="Add a success page">
        Create a success page to display order confirmation messaging or order details to your customer. Associate this page with the Checkout Session `success_url`, which Stripe redirects to after the customer successfully completes the checkout.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/App.jsx" linesStart={39} linesEnd={41} title="Add a customer portal button">
        Add a button to redirect to the customer portal to allow customers to manage their subscription. Clicking this button redirects your customer to the Stripe-hosted customer portal page.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="src/App.jsx" linesStart={32} linesEnd={42} title="Redirect to the customer portal session">
        Make a request to the endpoint on your server to redirect to a new customer portal session. This example uses the `session_id` from the Checkout session to demonstrate retrieving the `customer_id`. In a production environment, we recommend that you store this value alongside the authenticated user in your database.
      </CodeFocusSection>

      ### ❸ Call the Stripe API

      <CodeFocusSection activeFilePath="server.js" linesStart={1} linesEnd={2} title="Install the Stripe Node library">
        Install the package and import it in your code. Alternatively, if you're starting from scratch and need a package.json file, download the project files using the Download link in the code editor.

        **Install the library:**

        ```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
        npm install --save stripe
        ```
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={16} linesEnd={37} title="Create a Checkout Session">
        The Checkout Session controls what your customer sees in the Stripe-hosted payment page such as line items, the order amount and currency, and acceptable payment methods.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={12} linesEnd={15} title="Get the price from lookup key">
        Pass the lookup key you defined for your product in the Price endpoint to apply its price to the order.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={18} linesEnd={24} title="Define the line items">
        Always keep sensitive information about your product inventory, such as price and availability, on your server to prevent customer manipulation from the client. Pass in the predefined price ID retrieved above.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={26} linesEnd={26} title="Set the mode">
        Set the mode to `subscription`. Checkout also supports payment and setup modes for non-recurring payments.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={27} linesEnd={27} title="Supply success URL">
        Specify a publicly accessible URL that Stripe can redirect customers after success. Add the `session_id` query parameter at the end of your URL so you can retrieve the customer later and so Stripe can generate the customer's hosted Dashboard.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={37} linesEnd={37} title="Redirect from Checkout">
        After creating the session, redirect your customer to the URL returned in the response (either the success or cancel URL).
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={46} linesEnd={53} title="Create a customer portal session">
        Initiate a secure, Stripe-hosted customer portal session that lets your customers manage their subscriptions and billing details.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={55} linesEnd={56} title="Redirect to customer portal">
        After creating the portal session, redirect your customer to the URL returned in the response.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={58} linesEnd={129} title="Fulfil the subscription">
        Create a `/webhook` endpoint and obtain your webhook secret key in the Webhooks tab in Workbench to listen for events related to subscription activity. After a successful payment and redirect to the success page, verify that the subscription status is `active` and grant your customer access to the products and features they subscribed to.
      </CodeFocusSection>

      ### ❹ Test your page

      <CodeFocusSection activeFilePath="server.js" linesStart={131} linesEnd={131} title="Run the server">
        Start your server. It automatically opens a browser window to `http://localhost:3000/checkout`

        ```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
        npm start
        ```
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={131} linesEnd={131} title="Try it out">
        Click the checkout button. In the Stripe-hosted payment page, use any of these test cards to simulate a payment.

        **Payment succeeds**
        `4242 4242 4242 4242`

        **Payment requires authentication**
        `4000 0025 0000 3155`

        **Payment is declined**
        `4000 0000 0000 9995`
      </CodeFocusSection>

      ### Add customisation features

      <CodeFocusSection title="Add customisation features">
        If you successfully subscribed to your product in your test, you have a working, basic subscriptions checkout integration. Use the toggles below to see how to customise this sample with additional features.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={28} linesEnd={30} title="Add a trial period">
        Use `subscription_data` to add an integer representing the number of `trial_period_days` before charging the customer for the first time. This must be at least `1`.

        If you start a free trial without a payment method, set the `trial_settings[end_behavior][missing_payment_method]` field to `pause` or `cancel` so the subscription doesn't continue if the trial ends with no payment method. Pass this parameter into `subscription_data` when you create a Checkout session, or update it on the subscription at another time. See Use trial periods for more information.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={31} linesEnd={33} title="Anchor the subscription billing cycle">
        Use `subscription_data` to set a `billing_cycle_anchor` timestamp for a subscription's next billing date. See Setting the billing cycle date in Checkout for more information.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" linesStart={34} linesEnd={34} title="Add the automatic tax parameter">
        Set the `automatic_tax` parameter to `enabled: true`.
      </CodeFocusSection>

      ### Next steps

      <CodeFocusSection activeFilePath="server.js" title="Update subscription prices">
        Update subscriptions to handle customers upgrading or downgrading their pricing plan.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" title="Apply pro-rata calculations">
        Learn how to adjust a customer's invoice to accurately reflect mid-cycle pricing changes.
      </CodeFocusSection>

      <CodeFocusSection activeFilePath="server.js" title="Offer upsells">
        Incentivise customers with discounts for committing to longer billing intervals.
      </CodeFocusSection>
    </FrameworkGuide>
  </Architecture>
</SideBySideLayout>
