marketplace.tsx

  1import React, { useState, useEffect } from "react";
  2import Layout from "@theme/Layout";
  3import styles from "./marketplace.module.css";
  4
  5interface Plugin {
  6  name: string;
  7  title: string;
  8  description: string;
  9  file: string;
 10  url?: string;
 11}
 12
 13const REGISTRY_URL =
 14  "https://raw.githubusercontent.com/floatpane/matcha/master/plugins/registry.json";
 15const RAW_BASE =
 16  "https://raw.githubusercontent.com/floatpane/matcha/master/plugins/";
 17
 18function pluginUrl(plugin: Plugin): string {
 19  return plugin.url || `${RAW_BASE}${plugin.file}`;
 20}
 21
 22function installCmd(plugin: Plugin): string {
 23  return `matcha install ${pluginUrl(plugin)}`;
 24}
 25
 26function CopyButton({ text }: { text: string }) {
 27  const [copied, setCopied] = useState(false);
 28
 29  const handleCopy = () => {
 30    navigator.clipboard.writeText(text);
 31    setCopied(true);
 32    setTimeout(() => setCopied(false), 2000);
 33  };
 34
 35  return (
 36    <button
 37      className={styles.copyButton}
 38      onClick={handleCopy}
 39      title="Copy to clipboard"
 40      type="button"
 41    >
 42      {copied ? "Copied!" : "Copy"}
 43    </button>
 44  );
 45}
 46
 47function PluginCard({ plugin }: { plugin: Plugin }) {
 48  const cmd = installCmd(plugin);
 49
 50  return (
 51    <div className={styles.card}>
 52      <h3 className={styles.cardTitle}>{plugin.title}</h3>
 53      <p className={styles.cardDescription}>{plugin.description}</p>
 54      <div className={styles.installLabel}>Install:</div>
 55      <div className={styles.installRow}>
 56        <code className={styles.installCommand}>{cmd}</code>
 57        <CopyButton text={cmd} />
 58      </div>
 59    </div>
 60  );
 61}
 62
 63export default function Marketplace(): React.JSX.Element {
 64  const [plugins, setPlugins] = useState<Plugin[]>([]);
 65  const [loading, setLoading] = useState(true);
 66  const [error, setError] = useState<string | null>(null);
 67
 68  useEffect(() => {
 69    fetch(REGISTRY_URL)
 70      .then((res) => {
 71        if (!res.ok)
 72          throw new Error(`Failed to fetch registry (${res.status})`);
 73        return res.json();
 74      })
 75      .then((data: Plugin[]) => {
 76        setPlugins(data);
 77        setLoading(false);
 78      })
 79      .catch((err) => {
 80        setError(err.message);
 81        setLoading(false);
 82      });
 83  }, []);
 84
 85  return (
 86    <Layout
 87      title="Plugin Marketplace"
 88      description="Browse and install Matcha plugins"
 89    >
 90      <div className={styles.marketplace}>
 91        <div className={styles.header}>
 92          <h1>Plugin Marketplace</h1>
 93          <p>
 94            Browse community plugins for Matcha. Click install commands to copy.
 95          </p>
 96        </div>
 97        {loading && <p className={styles.count}>Loading plugins...</p>}
 98        {error && <p className={styles.error}>Error: {error}</p>}
 99        {!loading && !error && (
100          <>
101            <p className={styles.count}>{plugins.length} plugins available</p>
102            <div className={styles.grid}>
103              {plugins.map((plugin) => (
104                <PluginCard key={plugin.name} plugin={plugin} />
105              ))}
106            </div>
107          </>
108        )}
109      </div>
110    </Layout>
111  );
112}