Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extensions list #35

Merged
merged 17 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
104 changes: 104 additions & 0 deletions packages/nextjs/components/ExtensionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { useState } from "react";
import { usePlausible } from "next-plausible";
import CopyToClipboard from "react-copy-to-clipboard";
import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline";
import { Address } from "~~/components/scaffold-eth";

type Extension = {
name: string;
description: string;
github: string;
installCommand: string;
builder: string;
coBuilders: string[];
youtube?: string;
};

export const ExtensionCard = ({ extension, isCurated }: { extension: Extension; isCurated: boolean }) => {
const [commandCopied, setCommandCopied] = useState(false);
const plausible = usePlausible();

const handleInstallClick = () => {
setCommandCopied(true);
setTimeout(() => {
setCommandCopied(false);
}, 800);

const githubRepo = isCurated
? `Curated/${extension.name}`
: extension.github.split("github.com/")[1] || extension.github;

// Track the click event with GitHub repo as a prop
plausible("extensionCopyClick", { props: { id: githubRepo } });
};

return (
Pabl0cks marked this conversation as resolved.
Show resolved Hide resolved
<div className="card bg-base-100 shadow-xl mb-8 flex flex-col mx-1">
<div className="card-body flex-grow">
<h2 className="card-title">{extension.name}</h2>
<div className="flex justify-between items-start mb-2">
<div className="flex items-center gap-2">
{extension.github && (
<a href={extension.github} className="inline-block" target="_blank" rel="noopener noreferrer">
{
// eslint-disable-next-line
<img alt="github icon" className="w-6 h-6" src="/icon-github.svg" />
Pabl0cks marked this conversation as resolved.
Show resolved Hide resolved
}
</a>
)}
{extension.youtube && (
<a href={extension.youtube} className="inline-block" target="_blank" rel="noopener noreferrer">
{
// eslint-disable-next-line
<img alt="youtube icon" className="w-6 h-6" src="/icon-youtube.svg" />
}
</a>
)}
</div>
{isCurated ? (
<div className="badge badge-secondary p-3">Curated</div>
) : (
<div>
<Address address={extension.builder} disableAddressLink />
{extension.coBuilders && extension.coBuilders.length > 0 && (
<div className="text-sm mt-2">
{extension.coBuilders.map((coBuilder, index) => (
<Address key={index} address={coBuilder} disableAddressLink />
))}
</div>
)}
</div>
)}
</div>
<p
className="overflow-hidden flex-grow"
style={{
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 5,
maxHeight: "7.5em",
}}
>
{extension.description}
</p>
{!isCurated && (
<div className="mt-2 text-sm text-yellow-600 bg-yellow-100 p-2 rounded">
⚠️ 3rd-party extension. Verify the source before installing.
</div>
)}
</div>
<div className="card-actions mx-4 p-4 pt-0 pb-6 mt-auto">
<CopyToClipboard text={extension.installCommand} onCopy={handleInstallClick}>
<div className="flex items-center border-2 border-gray-300 rounded-xl px-3 sm:px-5 py-1 gap-2 cursor-pointer w-full">
<p className="m-0 text-center text-sm xl:text-base flex-grow">{extension.installCommand}</p>
{commandCopied ? (
<CheckCircleIcon className="text-xl font-normal h-6 w-4 flex-shrink-0" aria-hidden="true" />
) : (
<DocumentDuplicateIcon className="text-xl font-normal h-6 w-4 flex-shrink-0" aria-hidden="true" />
)}
</div>
</CopyToClipboard>
</div>
</div>
);
};
138 changes: 138 additions & 0 deletions packages/nextjs/pages/extensions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import type { GetStaticProps, NextPage } from "next";
import { MagnifyingGlassIcon } from "@heroicons/react/24/solid";
import { ExtensionCard } from "~~/components/ExtensionCard";
import { MetaHeader } from "~~/components/MetaHeader";
import curatedExtensions from "~~/public/extensions.json";

type Extension = {
name: string;
description: string;
github: string;
installCommand: string;
builder: string;
coBuilders: string[];
youtube?: string;
};

interface ExtensionsListProps {
thirdPartyExtensions: Extension[];
}

const ExtensionsList: NextPage<ExtensionsListProps> = ({ thirdPartyExtensions }) => {
const [searchQuery, setSearchQuery] = useState("");

const allExtensions = [...curatedExtensions.curated, ...thirdPartyExtensions];

const filteredExtensions = allExtensions.filter(extension => {
if (searchQuery.length < 3) return true;
const lowerCaseSearch = searchQuery.toLowerCase();
return (
extension.name.toLowerCase().includes(lowerCaseSearch) ||
extension.description.toLowerCase().includes(lowerCaseSearch)
);
});

return (
<>
<MetaHeader
title="Extensions List | Scaffold-ETH 2"
description="List of available extensions for Scaffold-ETH 2"
/>
<div className="container mx-auto p-4 min-h-screen flex flex-col -mb-16">
{/* Header section */}
<div className="flex items-center justify-between mb-8">
<Link href="/" className="flex items-center gap-2">
<div className="flex relative w-8 sm:w-10 h-8 sm:h-10">
<Image alt="SE2 logo" className="cursor-pointer" fill src="/logo.svg" />
</div>
<span className="text-xl sm:text-2xl font-medium">Scaffold-ETH 2</span>
</Link>
<Link href="/" className="btn btn-sm btn-ghost">
Back to Home
</Link>
</div>

<div className="flex flex-col items-center justify-center gap-4 mb-4">
<h1 className="text-3xl md:text-4xl font-bold text-center">Extensions List</h1>
<div className="relative w-full max-w-xs">
<input
type="text"
placeholder="Search extensions"
className="input input-bordered w-full pr-10 text-sm md:text-base"
onChange={e => setSearchQuery(e.target.value)}
/>
<MagnifyingGlassIcon className="absolute right-3 top-1/2 transform -translate-y-1/2 h-4 w-4 md:h-5 md:w-5 text-gray-400" />
</div>
</div>
<p className="text-base md:text-lg mb-8 text-center max-w-4xl mx-auto">
Explore our Curated (by BuidlGuidl) and community-contributed extensions for Scaffold-ETH 2.{" "}
<br className="hidden md:inline"></br> To install an extension, simply copy and run the installation command
provided for each extension.
</p>

{/* Combined extensions list */}
<div className="flex-grow">
{filteredExtensions.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pb-16">
{filteredExtensions.map((extension, index) => (
<ExtensionCard
key={index}
extension={extension}
isCurated={curatedExtensions.curated.includes(extension)}
/>
))}
</div>
) : (
<div className="flex items-center justify-center flex-grow">
<p className="text-center text-lg font-light">- No extensions found matching your search -</p>
</div>
)}
</div>
</div>
</>
);
};

// get third party extensions from buidlguidl app (builds with "extension" type)
export const getStaticProps: GetStaticProps<ExtensionsListProps> = async () => {
try {
const response = await fetch("https://buidlguidl-v3.ew.r.appspot.com/builds?type=extension");
Pabl0cks marked this conversation as resolved.
Show resolved Hide resolved
const data = await response.json();
const formattedExtensions = data.map((ext: any) => {
const githubUrlParts = ext.branch.split("/");
const githubUsername = githubUrlParts[3];
const repoName = githubUrlParts[4];

return {
name: ext.name,
description: ext.desc,
github: ext.branch,
installCommand: `npx create-eth@latest -e ${githubUsername}/${repoName}`,
builder: ext.builder,
coBuilders: ext.coBuilders || [],
youtube: ext.videoUrl || null,
};
});

return {
props: {
thirdPartyExtensions: formattedExtensions,
},
// Revalidate every 6 hours (21600 seconds)
revalidate: 21600,
};
} catch (error) {
console.error("Error fetching third-party extensions:", error);
return {
props: {
thirdPartyExtensions: [],
},
revalidate: 21600,
};
}
};

export default ExtensionsList;
17 changes: 12 additions & 5 deletions packages/nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,18 @@ const Home: NextPage = () => {
</div>
</CopyToClipboard>
</div>
<p className="m-auto text-center lg:text-left lg:mx-0 max-w-[400px] lg:max-w-none lg:pr-6 link">
<TrackedLink id="Extensions" href="https://docs.scaffoldeth.io/extensions/">
Learn more about extensions
</TrackedLink>
</p>
<div className="space-y-3">
<p className="m-auto text-center lg:text-left lg:mx-0 max-w-[400px] lg:max-w-none lg:pr-6 link">
<TrackedLink id="Extensions" href="https://docs.scaffoldeth.io/extensions/">
Learn more about extensions
</TrackedLink>
</p>
<p className="m-auto text-center lg:text-left lg:mx-0 max-w-[400px] lg:max-w-none lg:pr-6 link">
<TrackedLink id="ExtensionsList" href="/extensions">
Check out all the available extensions
</TrackedLink>
</p>
</div>
</div>
</div>
</div>
Expand Down
44 changes: 44 additions & 0 deletions packages/nextjs/public/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"curated": [
{
"name": "subgraph",
"description": "This Scaffold-ETH 2 extension helps you build and test subgraphs locally for your contracts. It also enables interaction with the front-end and facilitates easy deployment to Subgraph Studio.",
"github": "https://github.com/scaffold-eth/create-eth-extensions/tree/subgraph",
"installCommand": "npx create-eth@latest -e subgraph",
"builder": "0x1A2d838c4bbd1e73d162d0777d142c1d783Cb831",
"coBuilders": []
},
{
"name": "eip-712",
"description": "An implementation of EIP-712, allowing you to send, sign, and verify typed messages in a user-friendly manner.",
"github": "https://github.com/scaffold-eth/create-eth-extensions/tree/eip-712",
"installCommand": "npx create-eth@latest -e eip-712",
"builder": "0x1A2d838c4bbd1e73d162d0777d142c1d783Cb831",
"coBuilders": []
},
{
"name": "ponder",
"description": "This Scaffold-ETH 2 extension comes pre-configured with ponder.sh, providing an example to help you get started quickly.",
"github": "https://github.com/scaffold-eth/create-eth-extensions/tree/ponder",
"installCommand": "npx create-eth@latest -e ponder",
"builder": "0x5dCb5f4F39Caa6Ca25380cfc42280330b49d3c93",
"coBuilders": []
},
{
"name": "onchainkit",
"description": "This Scaffold-ETH 2 extension comes pre-configured with onchainkit, providing an example to help you get started quickly.",
"github": "https://github.com/scaffold-eth/create-eth-extensions/tree/onchainkit",
"installCommand": "npx create-eth@latest -e onchainkit",
"builder": "0x45334F41aAA464528CD5bc0F582acadC49Eb0Cd1",
"coBuilders": []
},
{
"name": "erc-20",
"description": "This extension introduces an ERC-20 token contract and demonstrates how to interact with it, including getting a holder balance and transferring tokens.",
"github": "https://github.com/scaffold-eth/create-eth-extensions/tree/erc-20",
"installCommand": "npx create-eth@latest -e erc-20",
"builder": "0x5dCb5f4F39Caa6Ca25380cfc42280330b49d3c93",
"coBuilders": ["0x60583563D5879C2E59973E5718c7DE2147971807"]
}
]
}
1 change: 1 addition & 0 deletions packages/nextjs/public/icon-github.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/nextjs/public/icon-youtube.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading