diff --git a/README.md b/README.md index 7e0b92134b6..0ae96348703 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ repository. Simply: To propose new docs or large edits to our existing pages, follow the steps accordingly: - **Ava Labs Github Organization Members:** Clone the repo - `git clone https://github.com/ava-labs/avalanche-docs.git` + `git clone https://github.com/ava-labs/builders-hub.git` - **External Contributors:** Fork the repo via GitHub's GUI - Checkout to a new branch `git checkout -b ` - Make changes on your branch @@ -38,7 +38,7 @@ To propose new docs or large edits to our existing pages, follow the steps accor - **`yarn dev` to ensure the build passes** - `git commit -m "some commit message"` - `git push` -- Head to [GitHub](https://github.com/ava-labs/avalanche-docs) and open a new pull request +- Head to [GitHub](https://github.com/ava-labs/builders-hub) and open a new pull request ### Structure and Syntax @@ -85,7 +85,7 @@ _The information I am requesting is related to a specific project, i.e. Avalanch _The information I am requesting is explanatory in nature and does not currently exist:_ -- Please open a new [Issue](https://github.com/ava-labs/avalanche-docs/issues/new/choose) +- Please open a new [Issue](https://github.com/ava-labs/builders-hub/issues/new/choose) in this repository and thoroughly detail your request according to the issue template. If urgent, please create a new ticket in the [Dev Docs Improvement Proposals](https://github.com/orgs/ava-labs/projects/15/views/1) @@ -95,5 +95,5 @@ _Erroneous or missing information on documentation unrelated to a specific proje editing:_ - If you understand the issue enough to provide a correction, follow the steps - [here](https://github.com/ava-labs/avalanche-docs#quick-fixes). -- If not, please raise an [Issue](https://github.com/ava-labs/avalanche-docs/issues/new/choose). + [here](https://github.com/ava-labs/builders-hub#quick-fixes). +- If not, please raise an [Issue](https://github.com/ava-labs/builders-hub/issues/new/choose). diff --git a/app/(home)/events/page.tsx b/app/(home)/events/page.tsx index 5bbb20e1a77..bb5f0dae45c 100644 --- a/app/(home)/events/page.tsx +++ b/app/(home)/events/page.tsx @@ -2,21 +2,26 @@ import Image from 'next/image' import { Button } from "@/components/ui/button" -import { ArrowRight, Zap, Cpu, Code, Coins, Bot, Home } from 'lucide-react' +import { ArrowRight, CalendarDays, Award, Users } from 'lucide-react' import { useTheme } from 'next-themes' import Link from 'next/link' -import { Banner } from "fumadocs-ui/components/banner" +import SummitLondonBanner from '@/public/nav-banner/avalanche_summit_london.png'; +import HackathonLondonBanner from '@/public/nav-banner/hackathon_luma.png'; +import HackathonBBABanner from '@/public/nav-banner/hackathon_bba.png'; +import Team1Banner from '@/public/nav-banner/local_events_team1.jpg'; -interface ProgramCardProps { +interface EventCardProps { title: string; description: string; icon: React.ReactNode; + image: any; + url: string; color: 'red' | 'blue' | 'green' | 'pink' | 'grey' | 'purple' | 'orange'; arrowColor: 'white' | 'black'; } -function ProgramCard({ title, description, icon, color, arrowColor }: ProgramCardProps) { - const gradients: Record = { +function EventCard({ title, description, icon, image, url, color, arrowColor }: EventCardProps) { + const gradients: Record = { red: "from-red-500 to-red-500", blue: "from-blue-500 to-cyan-500", green: "from-green-500 to-emerald-500", @@ -30,17 +35,25 @@ function ProgramCard({ title, description, icon, color, arrowColor }: ProgramCar
-
-
- {icon} +
+
+ {icon} +
+
- -
-

{title}

-

{description}

+
+ {title} +
+

{title}

+

{description}

) @@ -53,139 +66,111 @@ export default function Page() { return (
- {/* Hero Section */} -
-
- Avalanche Logo - Avalanche Logo -
-

- Grants & Programs - - Building the Future - -

-

- Empowering innovators to revolutionize blockchain technology with scalablable and sustainable solutions for real-world challenges. -

- + {/* Hero Section */} +
+
+ Avalanche Logo + Avalanche Logo +
+

+ Avalanche Events + + Connect & Build Together + +

+

+ Join the Avalanche community at our global events, hackathons, and meetups to connect, learn, and build the future of blockchain technology. +

+ - -
+ +
- {/* Prize Pool Section */} -
-
-
-
-
- 🏆 Total Funding Available -
-
+ {/* Featured Event Section */} +
+
+
+
+
+ 🗓️ Featured Event +
+

- $50M+ in Grants + Avalanche Summit London

- Fueling innovation across all programs based on project impact and potential. + Join us for the largest Avalanche event of the year, bringing together developers, investors, and innovators from around the world.

+ + +
- {/* Programs Grid */} -
-

Our Programs

- -
- - {/* CTA Section */} -
-
-
-

Avalanche Network [Security] Challenge

-

- Shape the future of blockchain security and earn massive rewards. Elite security researchers who identify critical vulnerabilities on Avalanche can claim bounties up to $100,000 USD. -

-
- - - - - - -
+ {/* Events Grid */} +
+

Upcoming Events

+
diff --git a/app/(home)/hackathon_bba/page.tsx b/app/(home)/hackathon_bba/page.tsx new file mode 100644 index 00000000000..4dc44fdbea3 --- /dev/null +++ b/app/(home)/hackathon_bba/page.tsx @@ -0,0 +1,370 @@ +"use client" +import React, { useState, ReactNode } from "react"; +import Link from 'next/link'; +import { ArrowUpRight, Code, Link as Zap, Link2, Lightbulb, X, Book, Users, SquareTerminal, Box, Mail, Network } from 'lucide-react'; +//import PartnerTracks from './partners'; +import { buttonVariants } from '@/components/ui/button'; + +const Card = ({ children, className = "", onClick = () => {} }: { children: ReactNode; className?: string; onClick?: () => void }) => ( +
+ {children} +
+); + +const Badge = ({ children, className = "" }: { children: React.ReactNode; className?: string }) => ( + + {children} + +); + +interface Resource { + name: string; + url: string; +} + +interface Track { + id: string; + title: string; + description: string; + difficulty: 'Beginner' | 'Intermediate' | 'Advanced' | 'Open'; + color: string; + icon: React.ReactNode; + challengeDetails?: string[]; + technologies?: { [key: string]: string | { description: string; skills?: string } }; + examples?: string[]; + resources?: Resource[]; +} + +const Modal: React.FC<{ isOpen: boolean; onClose: () => void; track: Track }> = ({ isOpen, onClose, track }) => { + if (!isOpen) return null; + + return ( +
+
+
+
+ {track.icon} +

{track.title}

+
+ +
+
+

{track.description}

+ + {track.challengeDetails && track.challengeDetails.length > 0 && ( +
+
    + {track.challengeDetails.map((detail, index) => ( +
  • {detail}
  • + ))} +
+
+ )} + + {track.technologies && Object.keys(track.technologies).length > 0 && ( +
+ {Object.entries(track.technologies).map(([name, details], index) => ( +
+

{name}

+

+ {typeof details === 'string' ? details : details.description} +

+ {typeof details !== 'string' && details.skills && ( +

Skills required: {details.skills}

+ )} +
+ ))} +
+ )} + + {track.examples && track.examples.length > 0 && ( +
+
    + {track.examples.map((example, index) => ( +
  • {example}
  • + ))} +
+
+ )} + + {track.resources && track.resources.length > 0 && ( +
+
    + {track.resources.map((resource, index) => ( +
  • + + {resource.name} + +
  • + ))} +
+
+ )} +
+
+
+ ); +}; + +const Section: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children }) => ( +
+

{title}

+ {children} +
+); + +const TrackCard: React.FC<{ track: Track }> = ({ track }) => { + const [isModalOpen, setIsModalOpen] = useState(false); + + const badgeColors = { + 'Beginner': 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900 dark:text-emerald-100', + 'Intermediate': 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-100', + 'Advanced': 'bg-rose-100 text-rose-800 dark:bg-rose-900 dark:text-rose-100', + 'Open': 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-100' + }; + + return ( + <> +
setIsModalOpen(true)} + > +
+ +
+
+
+
+ {track.icon} +
+

{track.title}

+
+

{track.description}

+ + {track.difficulty} + +
+
+ + setIsModalOpen(false)} track={track} /> + + ); +}; + +export default function HackathonPage() { + const tracks: Track[] = [ + { + id: 'dapp-l1', + title: 'dApps on Avalanche L1s', + description: "Deploy your dApp in your own Layer 1 blockchain to meet diverse technical requirements and reach scalability.", + difficulty: 'Intermediate', + color: 'border-red-500 text-red-700', + icon: , + examples: [ + 'Create any DeFI, RWA, Gaming, etc and deploy it on your own chain' + ], + resources: [ + { name: 'Multi-Chain Academy Course', url: 'https://build.avax.network/academy/multi-chain-architecture' }, + { name: 'Academy Starter-Kit', url: 'https://github.com/ava-labs/avalanche-starter-kit' } + ] + }, + { + id: 'cross-chain-dapps', + title: 'Cross-Chain Decentralized Applications (dApps)', + description: "Develop cross-chain decentralized applications on your own customizable L1 that seamlessly interacts with multiple blockchain networks using ICM and ICTT.", + difficulty: 'Intermediate', + color: 'border-green-500 text-green-700', + icon: , + examples: [ + 'Create DeFi, SocialFi, or Gaming dApps that operate across multiple Avalanche L1s and other blockchains.', + 'Develop applications utilizing staking, governance, or NFTs with cross-chain functionality.', + 'Integrate seamless user experiences with multi-network infrastructures.' + ], + resources: [ + { name: 'InterChain Messaging', url: 'https://academy.avax.network/course/interchain-messaging' }, + { name: 'InterChain Token Transfer', url: 'https://academy.avax.network/course/interchain-token-transfer' }, + { name: 'Cross-Chain Communication', url: 'https://build.avax.network/docs/build/avalanchego/cross-chain' } + ] + }, + { + id: 'tooling', + title: 'Developer Tooling', + description: "Leverage Avalanche's technology by creating developer tools that simplify building and deploying applications on Avalanche.", + difficulty: 'Intermediate', + color: 'border-purple-500 text-purple-700', + icon: , + examples: [ + 'Develop SDKs or APIs to improve developer experience.', + 'Build tools for smart contract deployment and testing.', + 'Create dashboards for network monitoring and analytics.' + ], + resources: [ + { name: 'Avalanche SDK', url: 'https://github.com/ava-labs/avalanchejs' }, + { name: 'Developer Tools', url: 'https://build.avax.network/docs/tools' } + ] + }, + { + id: 'ai-agents-avalanche', + title: 'AI Agents for Avalanche', + description: "Develop intelligent AI agents that interact with the Avalanche blockchain to automate tasks, analyze data, and enhance user experiences within decentralized ecosystems.", + difficulty: 'Advanced', + color: 'border-red-500 text-red-700', + icon: , + examples: [ + 'Create an agent that monitors Avalanche smart contracts and alerts users to significant events.', + 'Build an AI agent that analyzes transaction data for trends and insights.', + 'Develop agents that assist with wallet management and automate staking or bridging processes.' + ], + resources: [ + { name: 'AvalancheJS', url: 'https://github.com/ava-labs/avalanchejs' }, + { name: 'Avalanche Developer Documentation', url: 'https://build.avax.network/docs/' } + ] + }, + { + id: 'virtual-machines', + title: 'Custom Virtual Machines', + description: "Customize the execution layer of your blockchain by enhancing the EVM or creating dedicated virtual machines for specific functionalities, optimizing resource usage, and increasing TPS.", + difficulty: 'Advanced', + color: 'border-blue-500 text-blue-700', + icon: , + examples: [ + 'Add custom functionality to the EVM for specialized use cases.', + 'Develop dedicated virtual machines for optimized transaction processing.', + 'Utilize modular frameworks to modify transaction logic.' + ], + resources: [ + { name: 'Customize the EVM:', url: 'https://academy.avax.network/course/customizing-evm' }, + { name: 'Custom VM with HyperSDK:', url: 'https://github.com/ava-labs/hypersdk' } + ] + }, + ]; + + return ( +
+
+
+
+ Summit London Logo +

+ This hackathon is your chance to explore, build, and redefine what's possible on Avalanche. Below, you'll find different building blocks of the stack—from deploying dApps on Avalanche L1s to creating cross-chain applications, AI integrations, developer tooling, and even custom virtual machines. These are just starting points—you have the freedom to innovate and design solutions that bring real value to the ecosystem. Whether you're building in DeFi, RWA, Gaming, SocialFi, Institutional solutions, or something entirely new, this is your opportunity to experiment, create, and push blockchain technology forward. 🚀 +

+
+

Date: March 21-22, 2025

+
+
+
+ + Join the Hackathon Chat! → + +
+
+
+ +
+
+ +
+

+ 🏆 Prizes +

+
+ $7,500 Prize Pool +
+

+ Distributed across all tracks based on project impact and innovation. +

+
+

+ Top participants may earn a fast track interview in the{" "} + + + Codebase Incubator Program + + , gaining access to exclusive resources and mentorship. +

+
+
+
+
+
+ +
+
+

Hackathon Tracks

+ {/* +

+ To be Announced...Stay Tuned! +

+ */} + +
+ {tracks.map((track) => ( + + ))} +
+ +
+
+ + {/*
+
+ +
+
+ */} + + +
+
+

Resources and Support

+
+ +
+ +

Technical Mentorship

+

The DevRel team at Ava Labs will be available to guide teams on various technologies throughout the hackathon.

+
+
+ +
+ +

Workshops

+

Attend hands-on workshops on Avalanche technologies, cross-chain communication, blockchain customization, and data visualization.

+
+
+ +
+ +

Developer Resources

+
    +
  • + + Avalanche Docs + +
  • +
  • + + Avalanche Academy + +
  • +
  • + + Avalanche Faucet + +
  • +
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/(home)/page.client.tsx b/app/(home)/page.client.tsx deleted file mode 100644 index 2843ab013da..00000000000 --- a/app/(home)/page.client.tsx +++ /dev/null @@ -1,98 +0,0 @@ -'use client'; -import { TerminalIcon } from 'lucide-react'; -import React, { - useEffect, - useState, - Fragment, - type ReactElement, -} from 'react'; - - -export function DeployBlockchainAnimation(): React.ReactElement { - const installCmd = 'avalanche blockchain deploy myblockchain'; - const tickTime = 50; - const timeCommandEnter = installCmd.length; - const timeCommandRun = timeCommandEnter + 3; - const timeCommandEnd = timeCommandRun + 3; - const timeWindowOpen = timeCommandEnd + 1; - const timeEnd = timeWindowOpen + 1; - - const [tick, setTick] = useState(timeEnd); - - useEffect(() => { - const timer = setInterval(() => { - setTick((prev) => (prev >= timeEnd ? prev : prev + 1)); - }, tickTime); - - return () => { - clearInterval(timer); - }; - }, [timeEnd]); - - const lines: ReactElement[] = []; - - lines.push( - - {installCmd.substring(0, tick)} - {tick < timeCommandEnter && ( -
- )} - , - ); - - if (tick >= timeCommandEnter) { - lines.push( ); - } - - if (tick > timeCommandRun) - lines.push( - - - ? Choose a network for the operation: - - {tick > timeCommandRun + 1 && ( - <> - Local Network - Devnet - Fuji Testnet - Mainnet - - )} - {tick > timeCommandRun + 2 && ( - <> -
- Deploying [myblockchain] to Local Network... - - )} - {tick > timeCommandRun + 3 && ( - <> -
- Blockchain ready to use - - )} -
, - ); - - return ( -
{ - if (tick >= timeEnd) { - setTick(0); - } - }} - > -
-        
- {' '} - Avalanche CLI -
-
-
-
- {lines} -
-
-
- ); -} \ No newline at end of file diff --git a/app/(home)/tools/l1-toolbox/page.tsx b/app/(home)/tools/l1-toolbox/page.tsx index 68928006f61..2426256adf2 100644 --- a/app/(home)/tools/l1-toolbox/page.tsx +++ b/app/(home)/tools/l1-toolbox/page.tsx @@ -1,13 +1,16 @@ 'use client' import dynamic from 'next/dynamic'; -import { default as L1Toolbox } from "@/toolbox/src/demo/ToolboxApp" -export default function L1LauncherPage() { - const NoSSRL1Toolbox = dynamic(() => Promise.resolve(L1Toolbox), { ssr: false }); +// Import the component lazily with no SSR +const L1Toolbox = dynamic(() => import("@/toolbox/src/demo/ToolboxApp"), { + ssr: false, + loading: () =>
Loading...
+}); +export default function L1LauncherPage() { return (
- +
); } diff --git a/app/academy/[...slug]/page.tsx b/app/academy/[...slug]/page.tsx index 2ec18392779..86de26d6ac9 100644 --- a/app/academy/[...slug]/page.tsx +++ b/app/academy/[...slug]/page.tsx @@ -32,8 +32,8 @@ import { Pre, } from "fumadocs-ui/components/codeblock"; import Mermaid from "@/components/content-design/mermaid"; -import EditOnGithubButton from '@/components/ui/edit-on-github-button'; -import ReportIssueButton from "@/components/ui/report-issue-button"; +import { Feedback } from '@/components/ui/feedback'; +import posthog from 'posthog-js'; export const dynamicParams = false; @@ -51,7 +51,6 @@ export default async function Page(props: { return ( ), }}/> -
- - -
+ { + 'use server'; + await posthog.capture('on_rate_document', feedback); + }} + />
); } diff --git a/app/docs/[...slug]/page.tsx b/app/docs/[...slug]/page.tsx index 6df33846a25..a48207441c0 100644 --- a/app/docs/[...slug]/page.tsx +++ b/app/docs/[...slug]/page.tsx @@ -26,9 +26,9 @@ import { BackToTop } from '@/components/ui/back-to-top'; import { File, Folder, Files } from 'fumadocs-ui/components/files'; import Mermaid from "@/components/content-design/mermaid"; import type { MDXComponents } from 'mdx/types'; -import EditOnGithubButton from "@/components/ui/edit-on-github-button"; -import ReportIssueButton from "@/components/ui/report-issue-button"; import YouTube from '@/components/content-design/youtube'; +import { Feedback } from '@/components/ui/feedback'; +import posthog from 'posthog-js'; export const dynamicParams = false; export const revalidate = false; @@ -40,14 +40,12 @@ export default async function Page(props: { const page = documentation.getPage(params.slug); if (!page) notFound(); - const { body: MDX, toc, lastModified } = await page.data.load(); - + const { body: MDX, toc } = await page.data.load(); const path = `content/docs/${page.file.path}`; return ( , }} /> -
- - -
{page.data.index ? : null} + { + 'use server'; + await posthog.capture('on_rate_document', feedback); + }} + />
); } diff --git a/app/guides/[...slug]/page.tsx b/app/guides/[...slug]/page.tsx index 1b318cc1b1d..c3f3aaa43df 100644 --- a/app/guides/[...slug]/page.tsx +++ b/app/guides/[...slug]/page.tsx @@ -5,7 +5,6 @@ import Link from 'next/link'; import { guide } from '@/lib/source'; import { createMetadata } from '@/utils/metadata'; import { buttonVariants } from '@/components/ui/button'; -import { ArrowUpRightIcon, MessagesSquare, AlertCircle } from 'lucide-react'; import { Card, Cards } from 'fumadocs-ui/components/card'; import { Popup, PopupContent, PopupTrigger } from 'fumadocs-twoslash/ui'; import { Accordion, Accordions } from "fumadocs-ui/components/accordion"; @@ -25,8 +24,8 @@ import { } from "fumadocs-ui/components/codeblock"; import { BadgeCheck } from "lucide-react"; import Mermaid from "@/components/content-design/mermaid"; -import Comments from '@/components/ui/comments'; -import newGithubIssueUrl from 'new-github-issue-url'; +import { Feedback } from '@/components/ui/feedback'; +import posthog from 'posthog-js'; export const dynamicParams = false; @@ -81,9 +80,15 @@ export default async function Page(props: { ), }}/> - {page.data.comments && ( - }> - )} + { + 'use server'; + await posthog.capture('on_rate_document', feedback); + }} + />
@@ -120,29 +125,6 @@ export default async function Page(props: { ))}
- - - Edit on Github - - - Report Issue -
diff --git a/app/integrations/[...slug]/page.tsx b/app/integrations/[...slug]/page.tsx index 0c08893eb67..12d68b5a484 100644 --- a/app/integrations/[...slug]/page.tsx +++ b/app/integrations/[...slug]/page.tsx @@ -6,8 +6,8 @@ import { createMetadata } from '@/utils/metadata'; import { buttonVariants } from '@/components/ui/button'; import { Pill, Pills } from '@/components/ui/pills'; import defaultMdxComponents from 'fumadocs-ui/mdx'; -import EditOnGithubButton from '@/components/ui/edit-on-github-button'; -import ReportIssueButton from '@/components/ui/report-issue-button'; +import { Feedback } from '@/components/ui/feedback'; +import posthog from 'posthog-js'; export default async function Page(props: { params: Promise<{ slug: string[] }>; @@ -63,13 +63,15 @@ export default async function Page(props: {
-
- - -
+ { + 'use server'; + await posthog.capture('on_rate_document', feedback); + }} + />
diff --git a/app/integrations/page.tsx b/app/integrations/page.tsx index e8d91ade9b9..546bfd7216c 100644 --- a/app/integrations/page.tsx +++ b/app/integrations/page.tsx @@ -16,7 +16,7 @@ export default function Page(): React.ReactElement {

Discover Integrations - Add your Integration + Add your Integration
diff --git a/app/layout.config.tsx b/app/layout.config.tsx index 441be11723e..ac40adbce68 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -411,7 +411,7 @@ export const eventsMenu: LinkItemType = { }; export const baseOptions: BaseLayoutProps = { - githubUrl: 'https://github.com/ava-labs/avalanche-docs', + githubUrl: 'https://github.com/ava-labs/builders-hub', nav: { title: ( <> diff --git a/app/layout.tsx b/app/layout.tsx index 3e52e53cc50..4050fc220b8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -32,7 +32,7 @@ export default function Layout({ children }: { children: ReactNode }) { return ( - + {children} diff --git a/app/not-found.tsx b/app/not-found.tsx index b289fb4f640..701c146d387 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -17,7 +17,7 @@ function createGitHubIssueURL(path: string | null) { `Please provide any additional context about what you were looking for.` ); - return `https://github.com/ava-labs/avalanche-docs/issues/new?title=${title}&body=${body}&labels=bug`; + return `https://github.com/ava-labs/builders-hub/issues/new?title=${title}&body=${body}&labels=bug`; } export default function NotFound() { diff --git a/components/tools/PoA-Validator-Management/01_Welcome/Welcome.tsx b/components/tools/PoA-Validator-Management/01_Welcome/Welcome.tsx index 72f84789f2c..ada5eea8210 100644 --- a/components/tools/PoA-Validator-Management/01_Welcome/Welcome.tsx +++ b/components/tools/PoA-Validator-Management/01_Welcome/Welcome.tsx @@ -23,7 +23,7 @@ export default function Welcome() { The PoA Manager helps you manage your L1's validator set. You can add new validators, disable existing ones, and adjust their weight. This tool is designed for developers who need to maintain their Proof of Authority (PoA) validator set through a simple web interface.

- The tool is completely open source and available on GitHub + The tool is completely open source and available on GitHub

diff --git a/components/tools/common/ui/ToolHeader.tsx b/components/tools/common/ui/ToolHeader.tsx index 9da1918de50..08091f607bb 100644 --- a/components/tools/common/ui/ToolHeader.tsx +++ b/components/tools/common/ui/ToolHeader.tsx @@ -31,7 +31,7 @@ const ToolHeader: React.FC = ({ {duration}
- + diff --git a/components/ui/edit-on-github-button.tsx b/components/ui/edit-on-github-button.tsx deleted file mode 100644 index 9bd748bfa07..00000000000 --- a/components/ui/edit-on-github-button.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { PencilIcon } from 'lucide-react'; -import { cn } from '@/utils/cn'; -import { buttonVariants } from '@/components/ui/button'; - -interface EditOnGithubButtonProps { - path: string; -} - -export default function EditOnGithubButton({ path }: EditOnGithubButtonProps) { - return ( - - Edit on GitHub - - ); -} \ No newline at end of file diff --git a/components/ui/feedback.tsx b/components/ui/feedback.tsx new file mode 100644 index 00000000000..1117740cc36 --- /dev/null +++ b/components/ui/feedback.tsx @@ -0,0 +1,200 @@ +'use client'; +import { cn } from '@/utils/cn'; +import { buttonVariants } from 'fumadocs-ui/components/ui/button'; +import { ThumbsDown, ThumbsUp, PencilIcon, AlertCircle } from 'lucide-react'; +import { type SyntheticEvent, useEffect, useState } from 'react'; +import { + Collapsible, + CollapsibleContent, +} from 'fumadocs-ui/components/ui/collapsible'; +import { cva } from 'class-variance-authority'; +import { usePathname } from 'next/navigation'; +import newGithubIssueUrl from 'new-github-issue-url'; + +const rateButtonVariants = cva( + 'inline-flex items-center gap-2 px-3 py-2 rounded-full font-medium border text-sm [&_svg]:size-4 disabled:cursor-not-allowed transition-colors hover:bg-fd-accent/80 hover:text-fd-accent-foreground', + { + variants: { + active: { + true: 'bg-fd-accent text-fd-accent-foreground [&_svg]:fill-current', + false: 'text-fd-muted-foreground', + }, + }, + }, +); + +export interface Feedback { + opinion: 'yes' | 'no'; + message: string; +} + +function get(url: string): Feedback | null { + const item = localStorage.getItem(`document-feedback-${url}`); + + if (item === null) return null; + return JSON.parse(item) as Feedback; +} + +function set(url: string, feedback: Feedback | null) { + const key = `document-feedback-${url}`; + if (feedback) localStorage.setItem(key, JSON.stringify(feedback)); + else localStorage.removeItem(key); +} + +export interface UnifiedFeedbackProps { + onRateAction: (url: string, feedback: Feedback) => Promise; + path: string; + title: string; + pagePath: string; +} + +export function Feedback({ + onRateAction, + path, + title, + pagePath, +}: UnifiedFeedbackProps) { + const pathname = usePathname(); + const [previous, setPrevious] = useState(null); + const [opinion, setOpinion] = useState<'yes' | 'no' | null>(null); + const [message, setMessage] = useState(''); + + useEffect(() => { + setPrevious(get(pathname)); + }, [pathname]); + + function submit(e?: SyntheticEvent) { + e?.preventDefault(); + if (opinion == null) return; + + const feedback: Feedback = { + opinion, + message, + }; + + void onRateAction(pathname, feedback); + + set(pathname, feedback); + setPrevious(feedback); + setMessage(''); + setOpinion(null); + } + + return ( + { + if (!v) setOpinion(null); + }} + className="border-y py-3" + > +
+
+

Is this guide helpful?

+ + +
+ + +
+ + + {previous ? ( +
+

Thank you for your feedback!

+ +
+ ) : ( +
+