| 1 | import Link from "next/link"; |
| 2 | import { Icon } from "@/components/icon"; |
| 3 | import { PageHeader, Section } from "@/components/page-header"; |
| 4 | import { buildPageMetadata } from "@/lib/page-meta"; |
| 5 | import { PLUGINS_COPY, PLUGIN_COMMANDS } from "@/lib/content/plugins"; |
| 6 | import type { LocalizedText } from "@/lib/content/vocabulary"; |
| 7 | import { pickText } from "@/lib/i18n/dictionaries"; |
| 8 | |
| 9 | export const revalidate = 300; |
| 10 | |
| 11 | const MARKETPLACE_REPO = "https://github.com/codewhale-hq/codewhale-plugin-marketplace"; |
| 12 | const AUTHORING_DOC = "https://github.com/codewhale-hq/CodeWhale/blob/main/docs/PLUGIN_AUTHORING.md"; |
| 13 | |
| 14 | export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) { |
| 15 | const { locale } = await params; |
| 16 | return buildPageMetadata({ path: "/plugins", locale, |
| 17 | title: pickText(PLUGINS_COPY.metaTitle, locale), |
| 18 | description: pickText(PLUGINS_COPY.metaDescription, locale) }); |
| 19 | } |
| 20 | |
| 21 | /** |
| 22 | * /plugins — what a plugin adds, the marketplace catalogue, where plugins |
| 23 | * come from, how trust works, and the commands. Nothing runs until the |
| 24 | * reader reviews, trusts and enables it; the page says so where it matters. |
| 25 | */ |
| 26 | export default async function PluginsPage({ params }: { params: Promise<{ locale: string }> }) { |
| 27 | const { locale } = await params; |
| 28 | const t = (copy: LocalizedText) => pickText(copy, locale); |
| 29 | const authoringLink = ( |
| 30 | <Link href={AUTHORING_DOC} className="section-link"> |
| 31 | {t(PLUGINS_COPY.docsCta)} |
| 32 | <Icon name="external" className="icon" /> |
| 33 | </Link> |
| 34 | ); |
| 35 | |
| 36 | return ( |
| 37 | <> |
| 38 | <PageHeader |
| 39 | kicker={t(PLUGINS_COPY.kicker)} |
| 40 | title={t(PLUGINS_COPY.title)} |
| 41 | lede={t(PLUGINS_COPY.lead)} |
| 42 | pose="connect" |
| 43 | actions={ |
| 44 | <> |
| 45 | <Link href={MARKETPLACE_REPO} className="btn btn-primary btn-lg">{t(PLUGINS_COPY.marketplaceCta)}</Link> |
| 46 | <Link href={AUTHORING_DOC} className="btn btn-secondary btn-lg">{t(PLUGINS_COPY.docsCta)}</Link> |
| 47 | </> |
| 48 | } |
| 49 | /> |
| 50 | |
| 51 | <div className="page-body"> |
| 52 | <Section id="cu-title" title={t(PLUGINS_COPY.cuTitle)} scope={t(PLUGINS_COPY.cuLead)}> |
| 53 | <div className="split"> |
| 54 | <div className="stack"> |
| 55 | <pre tabIndex={0} className="code-block">{t(PLUGINS_COPY.cuInstall)}</pre> |
| 56 | <p className="section-scope">{t(PLUGINS_COPY.cuToolsNote)}</p> |
| 57 | <Link href={`/${locale}/computer-use`} className="section-link"> |
| 58 | Computer Use |
| 59 | <Icon name="arrow-right" className="icon icon-flip" /> |
| 60 | </Link> |
| 61 | </div> |
| 62 | <dl className="def-rows"> |
| 63 | {PLUGINS_COPY.cuFeatures.map((feature) => ( |
| 64 | <div key={t(feature.title)} className="def-row"> |
| 65 | <dt>{t(feature.title)}</dt> |
| 66 | <dd>{t(feature.detail)}</dd> |
| 67 | </div> |
| 68 | ))} |
| 69 | </dl> |
| 70 | </div> |
| 71 | </Section> |
| 72 | |
| 73 | <Section id="catalog-title" title={t(PLUGINS_COPY.catalogTitle)} scope={t(PLUGINS_COPY.catalogLead)}> |
| 74 | <pre tabIndex={0} className="code-block mb-4">{t(PLUGINS_COPY.catalogBrowse)}</pre> |
| 75 | <ul className="dir-list dir-list-card" role="list"> |
| 76 | {PLUGINS_COPY.catalogEntries.map((entry) => ( |
| 77 | <li key={entry.reference}> |
| 78 | <Link href={MARKETPLACE_REPO} className="dir-row"> |
| 79 | <span className="dir-mark" aria-hidden="true"><Icon name="plug" /></span> |
| 80 | <span className="dir-text"> |
| 81 | <span className="dir-title">{t(entry.title)}</span> |
| 82 | <span className="dir-purpose">{t(entry.detail)}</span> |
| 83 | <span className="dir-meta">{entry.reference}</span> |
| 84 | </span> |
| 85 | <span className="dir-action" aria-hidden="true"><Icon name="external" /></span> |
| 86 | </Link> |
| 87 | </li> |
| 88 | ))} |
| 89 | </ul> |
| 90 | <p className="section-scope mt-4">{t(PLUGINS_COPY.catalogNote)}</p> |
| 91 | </Section> |
| 92 | |
| 93 | <Section id="sources-title" title={t(PLUGINS_COPY.sourcesTitle)} scope={t(PLUGINS_COPY.sourcesLead)}> |
| 94 | <ul className="dir-list dir-list-card" role="list"> |
| 95 | {PLUGINS_COPY.sources.map((source) => ( |
| 96 | <li key={source.reference}> |
| 97 | <div className="dir-row"> |
| 98 | <span className="dir-mark" aria-hidden="true"><Icon name="package" /></span> |
| 99 | <span className="dir-text"> |
| 100 | <span className="dir-title">{t(source.title)}</span> |
| 101 | <span className="dir-purpose">{t(source.detail)}</span> |
| 102 | <span className="dir-meta">{source.reference}</span> |
| 103 | </span> |
| 104 | </div> |
| 105 | </li> |
| 106 | ))} |
| 107 | </ul> |
| 108 | </Section> |
| 109 | |
| 110 | <Section id="trust-title" title={t(PLUGINS_COPY.trustTitle)} scope={t(PLUGINS_COPY.trustLead)}> |
| 111 | <ol className="steps"> |
| 112 | {PLUGINS_COPY.trustSteps.map((step, index) => ( |
| 113 | <li key={t(step.title)}> |
| 114 | <span className="gs-step-index" aria-hidden="true">{index + 1}</span> |
| 115 | <h3>{t(step.title)}</h3> |
| 116 | <p>{t(step.detail)}</p> |
| 117 | </li> |
| 118 | ))} |
| 119 | </ol> |
| 120 | <p className="section-scope mt-4">{t(PLUGINS_COPY.trustNote)}</p> |
| 121 | </Section> |
| 122 | |
| 123 | <Section id="commands-title" title={t(PLUGINS_COPY.commandsTitle)} scope={t(PLUGINS_COPY.commandsLead)} link={authoringLink}> |
| 124 | <pre tabIndex={0} className="code-block">{PLUGIN_COMMANDS.join("\n")}</pre> |
| 125 | </Section> |
| 126 | </div> |
| 127 | </> |
| 128 | ); |
| 129 | } |
| 130 |