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}