Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions apps/website/app/changelog.md/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { readFileSync } from "fs";
import { join } from "path";
import { parseChangelog } from "@/utils/parse-changelog";

const BASE_URL = "https://react-grab.com";

export const dynamic = "force-static";

export const GET = (): Response => {
const changelogPath = join(process.cwd(), "..", "..", "packages", "react-grab", "CHANGELOG.md");
const content = readFileSync(changelogPath, "utf-8");
const entries = parseChangelog(content);

const lines: string[] = [];
lines.push("# Changelog");
lines.push("");
lines.push("> Release notes and version history for React Grab.");
lines.push("");
lines.push(`See the [HTML changelog](${BASE_URL}/changelog) for the rendered version.`);
lines.push("");

for (const entry of entries) {
lines.push(`## ${entry.version}`);
lines.push("");
if (entry.changeType) {
lines.push(`*${entry.changeType}*`);
lines.push("");
}
for (const change of entry.changes) {
lines.push(`- ${change}`);
}
lines.push("");
}

return new Response(lines.join("\n"), {
headers: {
"Content-Type": "text/markdown; charset=utf-8",
"Cache-Control": "public, max-age=300, s-maxage=300",
},
});
};
26 changes: 16 additions & 10 deletions apps/website/app/changelog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ const ogImageUrl = `https://react-grab.com/api/og?title=${encodeURIComponent(tit
export const metadata: Metadata = {
title: `${title} - React Grab`,
description,
alternates: {
canonical: "https://react-grab.com/changelog",
types: {
"text/markdown": "https://react-grab.com/changelog.md",
},
},
openGraph: {
title: `${title} - React Grab`,
description,
Expand Down Expand Up @@ -48,7 +54,7 @@ const ChangelogPage = () => {
const entries = getChangelog();

return (
<div className="min-h-screen bg-background px-4 py-6 sm:px-8 sm:py-8">
<main id="main-content" className="min-h-screen bg-background px-4 py-6 sm:px-8 sm:py-8">
<div className="mx-auto flex w-full max-w-2xl flex-col gap-2 pt-4 text-base sm:pt-8">
<Link
href="/"
Expand All @@ -71,18 +77,18 @@ const ChangelogPage = () => {
</div>

<div className="flex flex-col gap-1">
<div className="text-foreground font-bold">Changelog</div>
<div className="text-sm text-neutral-500">Release notes and version history</div>
<h1 className="text-foreground font-bold">Changelog</h1>
<p className="text-sm text-neutral-500">Release notes and version history</p>
</div>

<div className="flex flex-col mt-8 gap-8">
{entries.map((entry) => (
<div key={entry.version} className="flex flex-col gap-2">
<section key={entry.version} className="flex flex-col gap-2">
<div className="flex items-center gap-3">
<span className="text-foreground font-mono text-sm font-medium">
{entry.version}
</span>
<span className="text-neutral-600 text-xs">{entry.changeType}</span>
<h2 className="text-foreground font-mono text-sm font-medium">{entry.version}</h2>
{entry.changeType && (
<h3 className="text-neutral-600 text-xs font-normal">{entry.changeType}</h3>
)}
</div>
<ul className="flex flex-col gap-1.5">
{entry.changes.map((change, changeIndex) => (
Expand All @@ -95,11 +101,11 @@ const ChangelogPage = () => {
</li>
))}
</ul>
</div>
</section>
))}
</div>
</div>
</div>
</main>
);
};

Expand Down
59 changes: 59 additions & 0 deletions apps/website/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ const geistMono = Geist_Mono({
});

export const metadata: Metadata = {
metadataBase: new URL("https://react-grab.com"),
title: "React Grab",
description:
"Select an element → Give it to Cursor, Claude Code, etc → Make a change to your app",
alternates: {
canonical: "https://react-grab.com",
types: {
"text/markdown": "https://react-grab.com/index.md",
},
},
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
icons: {
icon: "https://react-grab.com/logo.png",
shortcut: "https://react-grab.com/logo.png",
Expand All @@ -53,16 +60,68 @@ export const metadata: Metadata = {
},
};

const STRUCTURED_DATA = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebSite",
"@id": "https://react-grab.com/#website",
url: "https://react-grab.com",
name: "React Grab",
description:
"Select an element → Give it to Cursor, Claude Code, etc → Make a change to your app",
inLanguage: "en-US",
publisher: { "@id": "https://react-grab.com/#organization" },
},
{
"@type": "Organization",
"@id": "https://react-grab.com/#organization",
name: "React Grab",
url: "https://react-grab.com",
logo: "https://react-grab.com/logo.png",
sameAs: ["https://github.com/aidenybai/react-grab"],
},
{
"@type": "SoftwareApplication",
name: "React Grab",
applicationCategory: "DeveloperApplication",
operatingSystem: "Web",
url: "https://react-grab.com",
description:
"Open-source dev-only script that adds an element picker to React apps so you can copy file paths, components, and HTML source for AI coding agents.",
offers: { "@type": "Offer", price: "0", priceCurrency: "USD" },
},
],
};

const RootLayout = ({
children,
}: Readonly<{
children: React.ReactNode;
}>) => {
return (
<html lang="en" className="dark">
<head>
<link
rel="alternate"
type="text/markdown"
href="https://react-grab.com/llms.txt"
title="llms.txt"
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(STRUCTURED_DATA) }}
/>
</head>
<body
className={`${geistSans.variable} ${geistMono.variable} ${caveat.variable} antialiased`}
>
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50 focus:rounded focus:bg-foreground focus:px-3 focus:py-2 focus:text-background"
>
Skip to content
</a>
<script src="/script.js" defer />
<NuqsAdapter>{children}</NuqsAdapter>
<Analytics />
Expand Down
9 changes: 8 additions & 1 deletion apps/website/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { HomepageDemo } from "@/components/homepage-demo";

const Home = () => {
return <HomepageDemo />;
return (
<main id="main-content">
<h1 className="sr-only">React Grab — Select React Elements for AI Coding Agents</h1>
<h2 className="sr-only">Pick any element, hand it to Cursor or Claude Code</h2>
<h3 className="sr-only">Install with npx grab init</h3>
<HomepageDemo />
</main>
);
};

Home.displayName = "Home";
Expand Down
28 changes: 17 additions & 11 deletions apps/website/app/privacy/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ const ogImageUrl = `https://react-grab.com/api/og?title=${encodeURIComponent(tit
export const metadata: Metadata = {
title: `${title} - React Grab`,
description,
alternates: {
canonical: "https://react-grab.com/privacy",
types: {
"text/markdown": "https://react-grab.com/privacy.md",
},
},
openGraph: {
title: `${title} - React Grab`,
description,
Expand All @@ -36,7 +42,7 @@ export const metadata: Metadata = {

const PrivacyPage = () => {
return (
<div className="min-h-screen bg-background px-4 py-6 sm:px-8 sm:py-8">
<main id="main-content" className="min-h-screen bg-background px-4 py-6 sm:px-8 sm:py-8">
<div className="mx-auto flex w-full max-w-2xl flex-col gap-2 pt-4 text-base sm:pt-8 sm:text-lg">
<Link
href="/"
Expand Down Expand Up @@ -72,7 +78,7 @@ const PrivacyPage = () => {
</section>

<section>
<p className="text-foreground font-bold mb-2">Data Collection</p>
<h2 className="text-foreground font-bold mb-2">Data Collection</h2>
<p className="mb-2">
React Grab does NOT collect, store, or transmit any personal data. Specifically:
</p>
Expand All @@ -86,7 +92,7 @@ const PrivacyPage = () => {
</section>

<section>
<p className="text-foreground font-bold mb-2">How React Grab Works</p>
<h2 className="text-foreground font-bold mb-2">How React Grab Works</h2>
<p className="mb-2">
React Grab operates entirely locally in your browser. When you use the extension:
</p>
Expand All @@ -99,7 +105,7 @@ const PrivacyPage = () => {
</section>

<section>
<p className="text-foreground font-bold mb-2">Permissions</p>
<h2 className="text-foreground font-bold mb-2">Permissions</h2>
<p className="mb-2">The extension requires the following permissions:</p>
<ul className="list-disc pl-6 space-y-1">
<li>
Expand All @@ -122,7 +128,7 @@ const PrivacyPage = () => {
</section>

<section>
<p className="text-foreground font-bold mb-2">Local Storage</p>
<h2 className="text-foreground font-bold mb-2">Local Storage</h2>
<p>
React Grab may store minimal settings locally on your device using browser storage
APIs. This data never leaves your device and can be cleared by uninstalling the
Expand All @@ -131,7 +137,7 @@ const PrivacyPage = () => {
</section>

<section>
<p className="text-foreground font-bold mb-2">Third-Party Services</p>
<h2 className="text-foreground font-bold mb-2">Third-Party Services</h2>
<p>
React Grab does not integrate with any third-party analytics, tracking, or advertising
services. The extension operates entirely offline and does not make any external
Expand All @@ -140,7 +146,7 @@ const PrivacyPage = () => {
</section>

<section>
<p className="text-foreground font-bold mb-2">Open Source</p>
<h2 className="text-foreground font-bold mb-2">Open Source</h2>
<p>
React Grab is open source software. You can review the complete source code on{" "}
<a
Expand All @@ -156,15 +162,15 @@ const PrivacyPage = () => {
</section>

<section>
<p className="text-foreground font-bold mb-2">Changes to This Policy</p>
<h2 className="text-foreground font-bold mb-2">Changes to This Policy</h2>
<p>
We may update this privacy policy from time to time. Any changes will be posted on
this page with an updated revision date.
</p>
</section>

<section>
<p className="text-foreground font-bold mb-2">Contact</p>
<h2 className="text-foreground font-bold mb-2">Contact</h2>
<p>
If you have questions about this privacy policy, please open an issue on our{" "}
<a
Expand All @@ -189,15 +195,15 @@ const PrivacyPage = () => {
</section>

<section className="pt-6 border-t border-border">
<p className="text-foreground font-bold mb-2">Summary</p>
<h2 className="text-foreground font-bold mb-2">Summary</h2>
<p>
React Grab respects your privacy. We don&apos;t collect, store, or transmit any of
your personal data. The extension works entirely locally on your device.
</p>
</section>
</div>
</div>
</div>
</main>
);
};

Expand Down
Loading
Loading