Selaa lähdekoodia

Merge pull request #2672 from pyth-network/devhub-boilerplate

Add Developer Hub to monorepo
Aaron Bassett 6 kuukautta sitten
vanhempi
sitoutus
af0448f51f
59 muutettua tiedostoa jossa 1439 lisäystä ja 88 poistoa
  1. 1 0
      .gitignore
  2. 2 0
      apps/developer-hub/.gitignore
  3. 7 0
      apps/developer-hub/.prettierignore
  4. 8 0
      apps/developer-hub/content/docs/entropy/how-to-guides/index.mdx
  5. 10 0
      apps/developer-hub/content/docs/entropy/index.mdx
  6. 7 0
      apps/developer-hub/content/docs/entropy/meta.json
  7. 8 0
      apps/developer-hub/content/docs/express-relay/how-to-guides/index.mdx
  8. 10 0
      apps/developer-hub/content/docs/express-relay/index.mdx
  9. 7 0
      apps/developer-hub/content/docs/express-relay/meta.json
  10. 8 0
      apps/developer-hub/content/docs/lazer/how-to-guides/index.mdx
  11. 10 0
      apps/developer-hub/content/docs/lazer/index.mdx
  12. 7 0
      apps/developer-hub/content/docs/lazer/meta.json
  13. 3 0
      apps/developer-hub/content/docs/meta.json
  14. 8 0
      apps/developer-hub/content/docs/pyth-core/how-to-guides/index.mdx
  15. 10 0
      apps/developer-hub/content/docs/pyth-core/index.mdx
  16. 7 0
      apps/developer-hub/content/docs/pyth-core/meta.json
  17. 1 0
      apps/developer-hub/eslint.config.js
  18. 1 0
      apps/developer-hub/jest.config.js
  19. 5 0
      apps/developer-hub/next-env.d.ts
  20. 54 0
      apps/developer-hub/next.config.js
  21. 63 0
      apps/developer-hub/package.json
  22. 5 0
      apps/developer-hub/postcss.config.mjs
  23. 1 0
      apps/developer-hub/prettier.config.js
  24. BIN
      apps/developer-hub/public/android-chrome-192x192.png
  25. BIN
      apps/developer-hub/public/android-chrome-512x512.png
  26. BIN
      apps/developer-hub/public/apple-touch-icon.png
  27. BIN
      apps/developer-hub/public/favicon-16x16.png
  28. BIN
      apps/developer-hub/public/favicon-32x32.png
  29. BIN
      apps/developer-hub/public/favicon-light.ico
  30. BIN
      apps/developer-hub/public/favicon.ico
  31. 30 0
      apps/developer-hub/source.config.ts
  32. 24 0
      apps/developer-hub/src/app/(docs)/[section]/[...slug]/page.tsx
  33. 23 0
      apps/developer-hub/src/app/(docs)/[section]/page.tsx
  34. 8 0
      apps/developer-hub/src/app/(docs)/layout.tsx
  35. 8 0
      apps/developer-hub/src/app/(homepage)/layout.tsx
  36. 1 0
      apps/developer-hub/src/app/(homepage)/page.tsx
  37. 5 0
      apps/developer-hub/src/app/api/search/route.ts
  38. 2 0
      apps/developer-hub/src/app/layout.ts
  39. 11 0
      apps/developer-hub/src/app/robots.ts
  40. 27 0
      apps/developer-hub/src/components/Pages/BasePage/index.tsx
  41. 10 0
      apps/developer-hub/src/components/Pages/DocumentationPage/index.tsx
  42. 5 0
      apps/developer-hub/src/components/Pages/Homepage/index.module.scss
  43. 9 0
      apps/developer-hub/src/components/Pages/Homepage/index.tsx
  44. 8 0
      apps/developer-hub/src/components/Pages/LandingPage/index.tsx
  45. 4 0
      apps/developer-hub/src/components/Root/global.css
  46. 36 0
      apps/developer-hub/src/components/Root/index.tsx
  47. 336 0
      apps/developer-hub/src/components/Root/theme.css
  48. 35 0
      apps/developer-hub/src/config/layout.config.tsx
  49. 29 0
      apps/developer-hub/src/config/server.ts
  50. 9 0
      apps/developer-hub/src/mdx-components.tsx
  51. 52 0
      apps/developer-hub/src/metadata.ts
  52. 32 0
      apps/developer-hub/src/source.ts
  53. 21 0
      apps/developer-hub/stylelint.config.js
  54. 6 0
      apps/developer-hub/svg.d.ts
  55. 5 0
      apps/developer-hub/tsconfig.json
  56. 44 0
      apps/developer-hub/turbo.json
  57. 4 0
      apps/developer-hub/vercel.json
  58. 406 88
      pnpm-lock.yaml
  59. 6 0
      pnpm-workspace.yaml

+ 1 - 0
.gitignore

@@ -25,3 +25,4 @@ __pycache__
 .turbo/
 .cursorrules
 .corepack
+justfile

+ 2 - 0
apps/developer-hub/.gitignore

@@ -0,0 +1,2 @@
+.env*.local
+.source

+ 7 - 0
apps/developer-hub/.prettierignore

@@ -0,0 +1,7 @@
+.next/
+coverage/
+node_modules/
+*.tsbuildinfo
+.env*.local
+.env
+.DS_Store

+ 8 - 0
apps/developer-hub/content/docs/entropy/how-to-guides/index.mdx

@@ -0,0 +1,8 @@
+---
+title: Entropy How-To Guide
+description: A placeholder docs page
+---
+
+# How To
+
+Build secure smart contracts with provably random numbers from Pyth Entropy. Launch NFTs, games, and other unique experiences that your users trust with seamless UX.

+ 10 - 0
apps/developer-hub/content/docs/entropy/index.mdx

@@ -0,0 +1,10 @@
+---
+title: Overview
+description: A placeholder landing page
+icon: CardsThree
+full: true
+---
+
+# Secure On-Chain Randomness
+
+Build secure smart contracts with provably random numbers from Pyth Entropy. Launch NFTs, games, and other unique experiences that your users trust with seamless UX.

+ 7 - 0
apps/developer-hub/content/docs/entropy/meta.json

@@ -0,0 +1,7 @@
+{
+  "root": true,
+  "title": "Entropy",
+  "description": "Random numbers for smart contracts",
+  "icon": "Shuffle",
+  "pages": ["index", "---Guides---", "how-to-guides"]
+}

+ 8 - 0
apps/developer-hub/content/docs/express-relay/how-to-guides/index.mdx

@@ -0,0 +1,8 @@
+---
+title: Express Relay How-To Guide
+description: A placeholder docs page
+---
+
+# How To
+
+Integrate directly with searchers to recapture MEV. Go to market faster. Accelerate your protocol's growth.

+ 10 - 0
apps/developer-hub/content/docs/express-relay/index.mdx

@@ -0,0 +1,10 @@
+---
+title: Overview
+description: A placeholder landing page
+icon: CardsThree
+full: true
+---
+
+# Take Back Control with Express Relay
+
+Integrate directly with searchers to recapture MEV. Go to market faster. Accelerate your protocol's growth.

+ 7 - 0
apps/developer-hub/content/docs/express-relay/meta.json

@@ -0,0 +1,7 @@
+{
+  "root": true,
+  "title": "Express Relay",
+  "description": "Eliminate MEV",
+  "icon": "Gavel",
+  "pages": ["index", "---Guides---", "how-to-guides"]
+}

+ 8 - 0
apps/developer-hub/content/docs/lazer/how-to-guides/index.mdx

@@ -0,0 +1,8 @@
+---
+title: Lazer How-To Guide
+description: A placeholder docs page
+---
+
+# How To
+
+Pyth Lazer is a low latency, highly customizable price oracle. It offers a customizable set of price feeds, target chains (EVM or Solana) and channels (real time or fixed rate)

+ 10 - 0
apps/developer-hub/content/docs/lazer/index.mdx

@@ -0,0 +1,10 @@
+---
+title: Overview
+description: A placeholder landing page
+icon: CardsThree
+full: true
+---
+
+# Low latency, highly customizable price oracle
+
+Pyth Lazer is a low latency, highly customizable price oracle. It offers a customizable set of price feeds, target chains (EVM or Solana) and channels (real time or fixed rate)

+ 7 - 0
apps/developer-hub/content/docs/lazer/meta.json

@@ -0,0 +1,7 @@
+{
+  "root": true,
+  "title": "Lazer",
+  "description": "Low latency, highly customizable price oracle",
+  "icon": "Lightning",
+  "pages": ["index", "---Guides---", "how-to-guides"]
+}

+ 3 - 0
apps/developer-hub/content/docs/meta.json

@@ -0,0 +1,3 @@
+{
+  "pages": ["pyth-core", "lazer", "express-relay", "entropy"]
+}

+ 8 - 0
apps/developer-hub/content/docs/pyth-core/how-to-guides/index.mdx

@@ -0,0 +1,8 @@
+---
+title: Pyth Core How-To Guide
+description: A placeholder docs page
+---
+
+# Heading One
+
+The fastest and most reliable data powering more transactions than any other oracle. Permissionless integration on every blockchain.

+ 10 - 0
apps/developer-hub/content/docs/pyth-core/index.mdx

@@ -0,0 +1,10 @@
+---
+title: Overview
+description: A placeholder landing page
+icon: CardsThree
+full: true
+---
+
+# Real-time data from financial institutions
+
+The fastest and most reliable data powering more transactions than any other oracle. Permissionless integration on every blockchain.

+ 7 - 0
apps/developer-hub/content/docs/pyth-core/meta.json

@@ -0,0 +1,7 @@
+{
+  "root": true,
+  "title": "Pyth Core",
+  "description": "Real-time data from financial institutions",
+  "icon": "ChartLine",
+  "pages": ["index", "---Guides---", "how-to-guides"]
+}

+ 1 - 0
apps/developer-hub/eslint.config.js

@@ -0,0 +1 @@
+export { nextjs as default } from "@cprussin/eslint-config";

+ 1 - 0
apps/developer-hub/jest.config.js

@@ -0,0 +1 @@
+export { nextjs as default } from "@cprussin/jest-config/next";

+ 5 - 0
apps/developer-hub/next-env.d.ts

@@ -0,0 +1,5 @@
+/// <reference types="next" />
+/// <reference types="next/image-types/global" />
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

+ 54 - 0
apps/developer-hub/next.config.js

@@ -0,0 +1,54 @@
+import { createMDX } from "fumadocs-mdx/next";
+
+const config = {
+  reactStrictMode: true,
+  pageExtensions: ["ts", "tsx", "mdx"],
+
+  logging: {
+    fetches: {
+      fullUrl: true,
+    },
+  },
+
+  webpack(config) {
+    config.module.rules.push({
+      test: /\.svg$/i,
+      use: ["@svgr/webpack"],
+    });
+
+    return config;
+  },
+
+  headers: async () => [
+    {
+      source: "/:path*",
+      headers: [
+        {
+          key: "X-XSS-Protection",
+          value: "1; mode=block",
+        },
+        {
+          key: "Referrer-Policy",
+          value: "strict-origin-when-cross-origin",
+        },
+        {
+          key: "Strict-Transport-Security",
+          value: "max-age=2592000",
+        },
+        {
+          key: "X-Content-Type-Options",
+          value: "nosniff",
+        },
+        {
+          key: "Permissions-Policy",
+          value:
+            "vibrate=(), geolocation=(), midi=(), notifications=(), push=(), sync-xhr=(), microphone=(), camera=(), magnetometer=(), gyroscope=(), speaker=(), vibrate=(), fullscreen=self",
+        },
+      ],
+    },
+  ],
+};
+
+const withMDX = createMDX();
+
+export default withMDX(config);

+ 63 - 0
apps/developer-hub/package.json

@@ -0,0 +1,63 @@
+{
+  "name": "@pythnetwork/developer-hub",
+  "version": "0.0.0",
+  "private": true,
+  "type": "module",
+  "engines": {
+    "node": "22"
+  },
+  "scripts": {
+    "build": "next build",
+    "build:analyze": "ANALYZE=true next build",
+    "fix:format": "prettier --write .",
+    "fix:lint:eslint": "eslint --fix .",
+    "fix:lint:stylelint": "stylelint --fix 'src/**/*.scss'",
+    "start:dev": "next dev --port 3627",
+    "start:prod": "next start --port 3627",
+    "test:format": "prettier --check .",
+    "test:lint:eslint": "eslint . --max-warnings 0",
+    "test:lint:stylelint": "stylelint 'src/**/*.scss' --max-warnings 0",
+    "test:types": "tsc"
+  },
+  "dependencies": {
+    "@phosphor-icons/react": "catalog:",
+    "@pythnetwork/component-library": "workspace:*",
+    "@react-hookz/web": "catalog:",
+    "clsx": "catalog:",
+    "fumadocs-core": "catalog:",
+    "fumadocs-mdx": "catalog:",
+    "fumadocs-ui": "catalog:",
+    "next": "catalog:",
+    "next-themes": "catalog:",
+    "nuqs": "catalog:",
+    "react": "catalog:",
+    "react-aria": "catalog:",
+    "react-dom": "catalog:",
+    "zod": "catalog:",
+    "zod-validation-error": "catalog:"
+  },
+  "devDependencies": {
+    "@cprussin/eslint-config": "catalog:",
+    "@cprussin/jest-config": "catalog:",
+    "@cprussin/prettier-config": "catalog:",
+    "@cprussin/tsconfig": "catalog:",
+    "@svgr/webpack": "catalog:",
+    "@tailwindcss/postcss": "catalog:",
+    "@types/jest": "catalog:",
+    "@types/mdx": "catalog:",
+    "@types/node": "catalog:",
+    "@types/react": "catalog:",
+    "@types/react-dom": "catalog:",
+    "autoprefixer": "catalog:",
+    "eslint": "catalog:",
+    "jest": "catalog:",
+    "postcss": "catalog:",
+    "prettier": "catalog:",
+    "sass": "catalog:",
+    "stylelint": "catalog:",
+    "stylelint-config-standard-scss": "catalog:",
+    "tailwindcss": "^4.1.6",
+    "typescript": "catalog:",
+    "vercel": "catalog:"
+  }
+}

+ 5 - 0
apps/developer-hub/postcss.config.mjs

@@ -0,0 +1,5 @@
+export default {
+  plugins: {
+    "@tailwindcss/postcss": {},
+  },
+};

+ 1 - 0
apps/developer-hub/prettier.config.js

@@ -0,0 +1 @@
+export { base as default } from "@cprussin/prettier-config";

BIN
apps/developer-hub/public/android-chrome-192x192.png


BIN
apps/developer-hub/public/android-chrome-512x512.png


BIN
apps/developer-hub/public/apple-touch-icon.png


BIN
apps/developer-hub/public/favicon-16x16.png


BIN
apps/developer-hub/public/favicon-32x32.png


BIN
apps/developer-hub/public/favicon-light.ico


BIN
apps/developer-hub/public/favicon.ico


+ 30 - 0
apps/developer-hub/source.config.ts

@@ -0,0 +1,30 @@
+import { defineConfig, defineDocs } from "fumadocs-mdx/config";
+import { z } from "zod";
+
+export const docs = defineDocs({
+  docs: {
+    schema: z.object({
+      title: z.string(),
+      description: z.string(),
+      icon: z.string().optional(),
+      full: z.boolean().default(false),
+      index: z.boolean().default(false),
+    }),
+  },
+  meta: {
+    schema: z.object({
+      title: z.string().optional(),
+      pages: z.array(z.string()).optional(),
+      description: z.string().optional(),
+      root: z.boolean().optional(),
+      defaultOpen: z.boolean().optional(),
+      icon: z.string().optional(),
+    }),
+  },
+});
+
+export default defineConfig({
+  mdxOptions: {
+    // MDX options
+  },
+});

+ 24 - 0
apps/developer-hub/src/app/(docs)/[section]/[...slug]/page.tsx

@@ -0,0 +1,24 @@
+export { DocumentationPage as default } from "../../../../components/Pages/DocumentationPage";
+import type { Metadata } from "next";
+import { notFound } from "next/navigation";
+
+import { source } from "../../../../source";
+
+export function generateStaticParams() {
+  return source.generateParams();
+}
+
+export async function generateMetadata(props: {
+  params: Promise<{ section: string; slug: string[] }>;
+}) {
+  const params = await props.params;
+
+  const page = source.getPage([params.section, ...params.slug]);
+
+  if (!page) notFound();
+
+  return {
+    title: page.data.title,
+    description: page.data.description,
+  } satisfies Metadata;
+}

+ 23 - 0
apps/developer-hub/src/app/(docs)/[section]/page.tsx

@@ -0,0 +1,23 @@
+export { LandingPage as default } from "../../../components/Pages/LandingPage";
+import type { Metadata } from "next";
+import { notFound } from "next/navigation";
+
+import { source } from "../../../source";
+
+export function generateStaticParams() {
+  return source.generateParams();
+}
+
+export async function generateMetadata(props: {
+  params: Promise<{ section: string }>;
+}) {
+  const params = await props.params;
+  const page = source.getPage([params.section]);
+
+  if (!page) notFound();
+
+  return {
+    title: page.data.title,
+    description: page.data.description,
+  } satisfies Metadata;
+}

+ 8 - 0
apps/developer-hub/src/app/(docs)/layout.tsx

@@ -0,0 +1,8 @@
+import { DocsLayout } from "fumadocs-ui/layouts/docs";
+import type { ReactNode } from "react";
+
+import { docsOptions } from "../../config/layout.config";
+
+export default function Layout({ children }: { children: ReactNode }) {
+  return <DocsLayout {...docsOptions}>{children}</DocsLayout>;
+}

+ 8 - 0
apps/developer-hub/src/app/(homepage)/layout.tsx

@@ -0,0 +1,8 @@
+import { HomeLayout } from "fumadocs-ui/layouts/home";
+import type { ReactNode } from "react";
+
+import { baseOptions } from "../../config/layout.config";
+
+export default function Layout({ children }: { children: ReactNode }) {
+  return <HomeLayout {...baseOptions}>{children}</HomeLayout>;
+}

+ 1 - 0
apps/developer-hub/src/app/(homepage)/page.tsx

@@ -0,0 +1 @@
+export { Homepage as default } from "../../components/Pages/Homepage";

+ 5 - 0
apps/developer-hub/src/app/api/search/route.ts

@@ -0,0 +1,5 @@
+import { createFromSource } from "fumadocs-core/search/server";
+
+import { source } from "../../../source";
+
+export const { GET } = createFromSource(source);

+ 2 - 0
apps/developer-hub/src/app/layout.ts

@@ -0,0 +1,2 @@
+export { Root as default } from "../components/Root";
+export { metadata, viewport } from "../metadata";

+ 11 - 0
apps/developer-hub/src/app/robots.ts

@@ -0,0 +1,11 @@
+import type { MetadataRoute } from "next";
+
+import { IS_PRODUCTION_SERVER } from "../config/server";
+
+const robots = (): MetadataRoute.Robots => ({
+  rules: {
+    userAgent: "*",
+    ...(IS_PRODUCTION_SERVER ? { allow: "/" } : { disallow: "/" }),
+  },
+});
+export default robots;

+ 27 - 0
apps/developer-hub/src/components/Pages/BasePage/index.tsx

@@ -0,0 +1,27 @@
+import {
+  DocsBody,
+  DocsDescription,
+  DocsPage,
+  DocsTitle,
+} from "fumadocs-ui/page";
+import { notFound } from "next/navigation";
+
+import { getMDXComponents } from "../../../mdx-components";
+import { source } from "../../../source";
+
+export function BasePage(props: { params: { slug: string[] } }) {
+  const page = source.getPage(props.params.slug);
+  if (!page) notFound();
+
+  const MDX = page.data.body;
+
+  return (
+    <DocsPage toc={page.data.toc} full={page.data.full}>
+      <DocsTitle>{page.data.title}</DocsTitle>
+      <DocsDescription>{page.data.description}</DocsDescription>
+      <DocsBody>
+        <MDX components={getMDXComponents()} />
+      </DocsBody>
+    </DocsPage>
+  );
+}

+ 10 - 0
apps/developer-hub/src/components/Pages/DocumentationPage/index.tsx

@@ -0,0 +1,10 @@
+import { BasePage } from "../BasePage";
+
+export async function DocumentationPage(props: {
+  params: Promise<{ section: string; slug: string[] }>;
+}) {
+  const params = await props.params;
+  return (
+    <BasePage params={{ ...params, slug: [params.section, ...params.slug] }} />
+  );
+}

+ 5 - 0
apps/developer-hub/src/components/Pages/Homepage/index.module.scss

@@ -0,0 +1,5 @@
+@use "@pythnetwork/component-library/theme";
+
+.landing {
+  @include theme.max-width;
+}

+ 9 - 0
apps/developer-hub/src/components/Pages/Homepage/index.tsx

@@ -0,0 +1,9 @@
+import styles from "./index.module.scss";
+
+export const Homepage = () => {
+  return (
+    <div className={styles.landing}>
+      <h2>Homepage Landing Page</h2>
+    </div>
+  );
+};

+ 8 - 0
apps/developer-hub/src/components/Pages/LandingPage/index.tsx

@@ -0,0 +1,8 @@
+import { BasePage } from "../BasePage";
+
+export async function LandingPage(props: {
+  params: Promise<{ section: string }>;
+}) {
+  const params = await props.params;
+  return <BasePage params={{ ...params, slug: [params.section] }} />;
+}

+ 4 - 0
apps/developer-hub/src/components/Root/global.css

@@ -0,0 +1,4 @@
+@import "tailwindcss";
+@import "fumadocs-ui/css/neutral.css";
+@import "fumadocs-ui/css/preset.css";
+@import "./theme.css";

+ 36 - 0
apps/developer-hub/src/components/Root/index.tsx

@@ -0,0 +1,36 @@
+import { AppShell } from "@pythnetwork/component-library/AppShell";
+import { RootProvider as FumadocsRootProvider } from "fumadocs-ui/provider";
+import { NuqsAdapter } from "nuqs/adapters/next/app";
+import type { ReactNode } from "react";
+import "./global.css";
+
+import {
+  AMPLITUDE_API_KEY,
+  ENABLE_ACCESSIBILITY_REPORTING,
+  GOOGLE_ANALYTICS_ID,
+} from "../../config/server";
+
+export const TABS = [
+  { segment: "", children: "Home" },
+  { segment: "pyth-core", children: "Pyth Core" },
+  { segment: "lazer", children: "Lazer" },
+  { segment: "express-relay", children: "Express Relay" },
+  { segment: "entropy", children: "Entropy" },
+];
+
+type Props = {
+  children: ReactNode;
+};
+
+export const Root = ({ children }: Props) => (
+  <AppShell
+    appName="Developer Hub"
+    amplitudeApiKey={AMPLITUDE_API_KEY}
+    googleAnalyticsId={GOOGLE_ANALYTICS_ID}
+    enableAccessibilityReporting={ENABLE_ACCESSIBILITY_REPORTING}
+    providers={[NuqsAdapter]}
+    tabs={TABS}
+  >
+    <FumadocsRootProvider>{children}</FumadocsRootProvider>
+  </AppShell>
+);

+ 336 - 0
apps/developer-hub/src/components/Root/theme.css

@@ -0,0 +1,336 @@
+:root {
+  --color-black: #000;
+  --color-white: #fff;
+
+  --color-slate-50: #f8fafc;
+  --color-slate-100: #f1f5f9;
+  --color-slate-200: #e2e8f0;
+  --color-slate-300: #cbd5e1;
+  --color-slate-400: #94a3b8;
+  --color-slate-500: #64748b;
+  --color-slate-600: #475569;
+  --color-slate-700: #334155;
+  --color-slate-800: #1e293b;
+  --color-slate-900: #0f172a;
+  --color-slate-950: #020617;
+
+  --color-gray-50: #f9fafb;
+  --color-gray-100: #f3f4f6;
+  --color-gray-200: #e5e7eb;
+  --color-gray-300: #d1d5db;
+  --color-gray-400: #9ca3af;
+  --color-gray-500: #6b7280;
+  --color-gray-600: #4b5563;
+  --color-gray-700: #374151;
+  --color-gray-800: #1f2937;
+  --color-gray-900: #111827;
+  --color-gray-950: #030712;
+
+  --color-zinc-50: #fafafa;
+  --color-zinc-100: #f4f4f5;
+  --color-zinc-200: #e4e4e7;
+  --color-zinc-300: #d4d4d8;
+  --color-zinc-400: #a1a1aa;
+  --color-zinc-500: #71717a;
+  --color-zinc-600: #52525b;
+  --color-zinc-700: #3f3f46;
+  --color-zinc-800: #27272a;
+  --color-zinc-900: #18181b;
+  --color-zinc-950: #09090b;
+
+  --color-neutral-50: #fafafa;
+  --color-neutral-100: #f5f5f5;
+  --color-neutral-200: #e5e5e5;
+  --color-neutral-300: #d4d4d4;
+  --color-neutral-400: #a3a3a3;
+  --color-neutral-500: #737373;
+  --color-neutral-600: #525252;
+  --color-neutral-700: #404040;
+  --color-neutral-800: #262626;
+  --color-neutral-900: #171717;
+  --color-neutral-950: #0a0a0a;
+
+  --color-stone-50: #fafaf9;
+  --color-stone-100: #f5f5f4;
+  --color-stone-200: #e7e5e4;
+  --color-stone-300: #d6d3d1;
+  --color-stone-400: #a8a29e;
+  --color-stone-500: #78716c;
+  --color-stone-600: #57534e;
+  --color-stone-700: #44403c;
+  --color-stone-800: #292524;
+  --color-stone-900: #1c1917;
+  --color-stone-950: #0c0a09;
+
+  --color-red-50: #fef2f2;
+  --color-red-100: #fee2e2;
+  --color-red-200: #fecaca;
+  --color-red-300: #fca5a5;
+  --color-red-400: #f87171;
+  --color-red-500: #ef4444;
+  --color-red-600: #dc2626;
+  --color-red-700: #b91c1c;
+  --color-red-800: #991b1b;
+  --color-red-900: #7f1d1d;
+  --color-red-950: #450a0a;
+
+  --color-orange-50: #fff7ed;
+  --color-orange-100: #ffedd5;
+  --color-orange-200: #fed7aa;
+  --color-orange-300: #fdba74;
+  --color-orange-400: #fb923c;
+  --color-orange-500: #f97316;
+  --color-orange-600: #ea580c;
+  --color-orange-700: #c2410c;
+  --color-orange-800: #9a3412;
+  --color-orange-900: #7c2d12;
+  --color-orange-950: #431407;
+
+  --color-amber-50: #fffbeb;
+  --color-amber-100: #fef3c7;
+  --color-amber-200: #fde68a;
+  --color-amber-300: #fcd34d;
+  --color-amber-400: #fbbf24;
+  --color-amber-500: #f59e0b;
+  --color-amber-600: #d97706;
+  --color-amber-700: #b45309;
+  --color-amber-800: #92400e;
+  --color-amber-900: #78350f;
+  --color-amber-950: #451a03;
+
+  --color-yellow-50: #fefce8;
+  --color-yellow-100: #fef9c3;
+  --color-yellow-200: #fef08a;
+  --color-yellow-300: #fde047;
+  --color-yellow-400: #facc15;
+  --color-yellow-500: #eab308;
+  --color-yellow-600: #ca8a04;
+  --color-yellow-700: #a16207;
+  --color-yellow-800: #854d0e;
+  --color-yellow-900: #713f12;
+  --color-yellow-950: #422006;
+
+  --color-lime-50: #f7fee7;
+  --color-lime-100: #ecfccb;
+  --color-lime-200: #d9f99d;
+  --color-lime-300: #bef264;
+  --color-lime-400: #a3e635;
+  --color-lime-500: #84cc16;
+  --color-lime-600: #65a30d;
+  --color-lime-700: #4d7c0f;
+  --color-lime-800: #3f6212;
+  --color-lime-900: #365314;
+  --color-lime-950: #1a2e05;
+
+  --color-green-50: #f0fdf4;
+  --color-green-100: #dcfce7;
+  --color-green-200: #bbf7d0;
+  --color-green-300: #86efac;
+  --color-green-400: #4ade80;
+  --color-green-500: #22c55e;
+  --color-green-600: #16a34a;
+  --color-green-700: #15803d;
+  --color-green-800: #166534;
+  --color-green-900: #14532d;
+  --color-green-950: #052e16;
+
+  --color-emerald-50: #ecfdf5;
+  --color-emerald-100: #d1fae5;
+  --color-emerald-200: #a7f3d0;
+  --color-emerald-300: #6ee7b7;
+  --color-emerald-400: #34d399;
+  --color-emerald-500: #10b981;
+  --color-emerald-600: #059669;
+  --color-emerald-700: #047857;
+  --color-emerald-800: #065f46;
+  --color-emerald-900: #064e3b;
+  --color-emerald-950: #022c22;
+
+  --color-teal-50: #f0fdfa;
+  --color-teal-100: #ccfbf1;
+  --color-teal-200: #99f6e4;
+  --color-teal-300: #5eead4;
+  --color-teal-400: #2dd4bf;
+  --color-teal-500: #14b8a6;
+  --color-teal-600: #0d9488;
+  --color-teal-700: #0f766e;
+  --color-teal-800: #115e59;
+  --color-teal-900: #134e4a;
+  --color-teal-950: #042f2e;
+
+  --color-cyan-50: #ecfeff;
+  --color-cyan-100: #cffafe;
+  --color-cyan-200: #a5f3fc;
+  --color-cyan-300: #67e8f9;
+  --color-cyan-400: #22d3ee;
+  --color-cyan-500: #06b6d4;
+  --color-cyan-600: #0891b2;
+  --color-cyan-700: #0e7490;
+  --color-cyan-800: #155e75;
+  --color-cyan-900: #164e63;
+  --color-cyan-950: #083344;
+
+  --color-sky-50: #f0f9ff;
+  --color-sky-100: #e0f2fe;
+  --color-sky-200: #bae6fd;
+  --color-sky-300: #7dd3fc;
+  --color-sky-400: #38bdf8;
+  --color-sky-500: #0ea5e9;
+  --color-sky-600: #0284c7;
+  --color-sky-700: #0369a1;
+  --color-sky-800: #075985;
+  --color-sky-900: #0c4a6e;
+  --color-sky-950: #082f49;
+
+  --color-blue-50: #eff6ff;
+  --color-blue-100: #dbeafe;
+  --color-blue-200: #bfdbfe;
+  --color-blue-300: #93c5fd;
+  --color-blue-400: #60a5fa;
+  --color-blue-500: #3b82f6;
+  --color-blue-600: #2563eb;
+  --color-blue-700: #1d4ed8;
+  --color-blue-800: #1e40af;
+  --color-blue-900: #1e3a8a;
+  --color-blue-950: #172554;
+
+  --color-indigo-50: #eef2ff;
+  --color-indigo-100: #e0e7ff;
+  --color-indigo-200: #c7d2fe;
+  --color-indigo-300: #a5b4fc;
+  --color-indigo-400: #818cf8;
+  --color-indigo-500: #6366f1;
+  --color-indigo-600: #4f46e5;
+  --color-indigo-700: #4338ca;
+  --color-indigo-800: #3730a3;
+  --color-indigo-900: #312e81;
+  --color-indigo-950: #1e1b4b;
+
+  --color-violet-50: #f5f3ff;
+  --color-violet-100: #ede9fe;
+  --color-violet-200: #ddd6fe;
+  --color-violet-300: #c4b5fd;
+  --color-violet-400: #a78bfa;
+  --color-violet-500: #8b5cf6;
+  --color-violet-600: #7c3aed;
+  --color-violet-700: #6d28d9;
+  --color-violet-800: #5b21b6;
+  --color-violet-900: #4c1d95;
+  --color-violet-950: #2e1065;
+
+  --color-purple-50: #faf5ff;
+  --color-purple-100: #f3e8ff;
+  --color-purple-200: #e9d5ff;
+  --color-purple-300: #d8b4fe;
+  --color-purple-400: #c084fc;
+  --color-purple-500: #a855f7;
+  --color-purple-600: #9333ea;
+  --color-purple-700: #7e22ce;
+  --color-purple-800: #6b21a8;
+  --color-purple-900: #581c87;
+  --color-purple-950: #3b0764;
+
+  --color-fuchsia-50: #fdf4ff;
+  --color-fuchsia-100: #fae8ff;
+  --color-fuchsia-200: #f5d0fe;
+  --color-fuchsia-300: #f0abfc;
+  --color-fuchsia-400: #e879f9;
+  --color-fuchsia-500: #d946ef;
+  --color-fuchsia-600: #c026d3;
+  --color-fuchsia-700: #a21caf;
+  --color-fuchsia-800: #86198f;
+  --color-fuchsia-900: #701a75;
+  --color-fuchsia-950: #4a044e;
+
+  --color-pink-50: #fdf2f8;
+  --color-pink-100: #fce7f3;
+  --color-pink-200: #fbcfe8;
+  --color-pink-300: #f9a8d4;
+  --color-pink-400: #f472b6;
+  --color-pink-500: #ec4899;
+  --color-pink-600: #db2777;
+  --color-pink-700: #be185d;
+  --color-pink-800: #9d174d;
+  --color-pink-900: #831843;
+  --color-pink-950: #500724;
+
+  --color-rose-50: #fff1f2;
+  --color-rose-100: #ffe4e6;
+  --color-rose-200: #fecdd3;
+  --color-rose-300: #fda4af;
+  --color-rose-400: #fb7185;
+  --color-rose-500: #f43f5e;
+  --color-rose-600: #e11d48;
+  --color-rose-700: #be123c;
+  --color-rose-800: #9f1239;
+  --color-rose-900: #881337;
+  --color-rose-950: #4c0519;
+
+  --color-beige-50: #f7f4f4;
+  --color-beige-100: #f3eded;
+  --color-beige-200: #e9dfdf;
+  --color-beige-300: #d9c8c8;
+  --color-beige-400: #c1a8a8;
+  --color-beige-500: #a98a8a;
+  --color-beige-600: #927070;
+  --color-beige-700: #795c5c;
+  --color-beige-800: #664e4e;
+  --color-beige-900: #574545;
+  --color-beige-950: #2d2222;
+
+  --color-steel-50: #f8f9fc;
+  --color-steel-100: #f1f2f9;
+  --color-steel-200: #e2e3f0;
+  --color-steel-300: #cbcee1;
+  --color-steel-400: #9497b8;
+  --color-steel-500: #64678b;
+  --color-steel-600: #474a69;
+  --color-steel-700: #333655;
+  --color-steel-800: #25253e;
+  --color-steel-900: #27253d;
+  --color-steel-950: #100e23;
+
+  --color-fd-background: light-dark(var(--color-white), var(--color-steel-950));
+  --color-fd-primary: light-dark(var(--color-steel-900), var(--color-steel-50));
+  --color-fd-border: light-dark(var(--color-stone-300), var(--color-steel-600));
+  --color-fd-accent: light-dark(
+    var(--color-violet-600),
+    var(--color-violet-500)
+  );
+  --color-fd-accent-foreground: light-dark(
+    var(--color-steel-900),
+    var(--color-steel-50)
+  );
+  --color-fd-muted: light-dark(var(--color-stone-700), var(--color-steel-400));
+  --color-fd-muted-foreground: light-dark(
+    var(--color-steel-600),
+    var(--color-steel-400)
+  );
+  --color-fd-foreground: light-dark(
+    var(--color-steel-900),
+    var(--color-steel-50)
+  );
+  --color-fd-secondary: light-dark(
+    var(--color-beige-100),
+    var(--color-steel-900)
+  );
+  --color-fd-secondary-foreground: light-dark(
+    var(--color-steel-800),
+    var(--color-steel-50)
+  );
+  --color-fd-card: light-dark(var(--color-white), var(--color-steel-950));
+  --color-fd-card-foreground: light-dark(
+    var(--color-steel-900),
+    var(--color-steel-50)
+  );
+  --color-fd-popover-foreground: light-dark(
+    var(--color-steel-900),
+    var(--color-steel-50)
+  );
+  --color-fd-popover: light-dark(var(--color-white), var(--color-steel-950));
+  --color-fd-primary-foreground: light-dark(
+    var(--color-steel-900),
+    var(--color-steel-50)
+  );
+  --color-fd-ring: light-dark(var(--color-violet-600), var(--color-violet-400));
+}

+ 35 - 0
apps/developer-hub/src/config/layout.config.tsx

@@ -0,0 +1,35 @@
+import type { DocsLayoutProps } from "fumadocs-ui/layouts/docs";
+import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
+
+import { source } from "../source";
+
+export const baseOptions: BaseLayoutProps = {
+  nav: {
+    enabled: false,
+  },
+  themeSwitch: {
+    enabled: false,
+  },
+};
+
+export const docsOptions: DocsLayoutProps = {
+  ...baseOptions,
+  tree: source.pageTree,
+  sidebar: {
+    tabs: {
+      transform(option, node) {
+        const meta = source.getNodeMeta(node);
+        if (!meta || !node.icon) return option;
+
+        return {
+          ...option,
+          icon: (
+            <div className="[&_svg]:size-6.5 md:[&_svg]:size-5">
+              {node.icon}
+            </div>
+          ),
+        };
+      },
+    },
+  },
+};

+ 29 - 0
apps/developer-hub/src/config/server.ts

@@ -0,0 +1,29 @@
+// Disable the following rule because this file is the intended place to declare
+// and load all env variables.
+/* eslint-disable n/no-process-env */
+
+import "server-only";
+
+const getEnvOrDefault = (key: string, defaultValue: string) =>
+  process.env[key] ?? defaultValue;
+
+/**
+ * Indicates that this server is the live customer-facing production server.
+ */
+export const IS_PRODUCTION_SERVER = process.env.VERCEL_ENV === "production";
+
+const defaultInProduction = IS_PRODUCTION_SERVER
+  ? getEnvOrDefault
+  : (key: string) => process.env[key];
+
+export const GOOGLE_ANALYTICS_ID = defaultInProduction(
+  "GOOGLE_ANALYTICS_ID",
+  "G-E1QSY256EQ",
+);
+export const AMPLITUDE_API_KEY = defaultInProduction(
+  "AMPLITUDE_API_KEY",
+  "6faa78c51eff33087eb19f0f3dc76f33",
+);
+
+export const ENABLE_ACCESSIBILITY_REPORTING =
+  !IS_PRODUCTION_SERVER && !process.env.DISABLE_ACCESSIBILITY_REPORTING;

+ 9 - 0
apps/developer-hub/src/mdx-components.tsx

@@ -0,0 +1,9 @@
+import defaultMdxComponents from "fumadocs-ui/mdx";
+import type { MDXComponents } from "mdx/types";
+
+export function getMDXComponents(components?: MDXComponents): MDXComponents {
+  return {
+    ...defaultMdxComponents,
+    ...components,
+  };
+}

+ 52 - 0
apps/developer-hub/src/metadata.ts

@@ -0,0 +1,52 @@
+import type { Metadata, Viewport } from "next";
+
+export const metadata = {
+  metadataBase: new URL("https://developer.pyth.network"),
+  title: {
+    default: "Pyth Developer Hub",
+    template: "%s | Pyth Developer Hub",
+  },
+  applicationName: "Pyth Developer Hub",
+  description:
+    "Learn more about Pyth and how to integrate into your application.",
+  referrer: "strict-origin-when-cross-origin",
+  openGraph: {
+    type: "website",
+  },
+  twitter: {
+    creator: "@PythNetwork",
+    card: "summary_large_image",
+  },
+  icons: {
+    icon: [
+      {
+        media: "(prefers-color-scheme: light)",
+        type: "image/x-icon",
+        url: "/favicon.ico",
+      },
+      {
+        media: "(prefers-color-scheme: dark)",
+        type: "image/x-icon",
+        url: "/favicon-light.ico",
+      },
+      {
+        type: "image/png",
+        sizes: "32x32",
+        url: "/favicon-32x32.png",
+      },
+      {
+        type: "image/png",
+        sizes: "16x16",
+        url: "/favicon-16x16.png",
+      },
+    ],
+    apple: {
+      url: "/apple-touch-icon.png",
+      sizes: "180x180",
+    },
+  },
+} satisfies Metadata;
+
+export const viewport = {
+  themeColor: "#242235",
+} satisfies Viewport;

+ 32 - 0
apps/developer-hub/src/source.ts

@@ -0,0 +1,32 @@
+import {
+  CardsThree,
+  ChartLine,
+  FolderSimpleDashed,
+  Gavel,
+  Lightning,
+  Shuffle,
+} from "@phosphor-icons/react/dist/ssr";
+import type { InferMetaType, InferPageType } from "fumadocs-core/source";
+import { loader } from "fumadocs-core/source";
+import { createElement } from "react";
+
+import { docs } from "../.source";
+
+const icons: Record<string, React.ComponentType> = {
+  CardsThree,
+  ChartLine,
+  Gavel,
+  Lightning,
+  Shuffle,
+};
+
+export const source = loader({
+  baseUrl: "/",
+  icon(icon) {
+    return icon ? createElement(icons[icon] ?? FolderSimpleDashed) : undefined;
+  },
+  source: docs.toFumadocsSource(),
+});
+
+export type Page = InferPageType<typeof source>;
+export type Meta = InferMetaType<typeof source>;

+ 21 - 0
apps/developer-hub/stylelint.config.js

@@ -0,0 +1,21 @@
+import standardScss from "stylelint-config-standard-scss";
+
+const config = {
+  extends: standardScss,
+  rules: {
+    "selector-class-pattern": [
+      "^[a-z][a-zA-Z0-9]+$",
+      {
+        message: (selector) =>
+          `Expected class selector "${selector}" to be camel-case`,
+      },
+    ],
+    "selector-pseudo-class-no-unknown": [
+      true,
+      {
+        ignorePseudoClasses: ["global", "export"],
+      },
+    ],
+  },
+};
+export default config;

+ 6 - 0
apps/developer-hub/svg.d.ts

@@ -0,0 +1,6 @@
+declare module "*.svg" {
+  import type { ReactElement, SVGProps } from "react";
+
+  const content: (props: SVGProps<SVGElement>) => ReactElement;
+  export default content;
+}

+ 5 - 0
apps/developer-hub/tsconfig.json

@@ -0,0 +1,5 @@
+{
+  "extends": "@cprussin/tsconfig/nextjs.json",
+  "include": ["svg.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+  "exclude": ["node_modules"]
+}

+ 44 - 0
apps/developer-hub/turbo.json

@@ -0,0 +1,44 @@
+{
+  "$schema": "https://turbo.build/schema.json",
+  "extends": ["//"],
+  "tasks": {
+    "build": {
+      "env": [
+        "VERCEL_ENV",
+        "GOOGLE_ANALYTICS_ID",
+        "AMPLITUDE_API_KEY",
+        "DISABLE_ACCESSIBILITY_REPORTING"
+      ]
+    },
+    "fix:lint": {
+      "dependsOn": [
+        "//#install:modules",
+        "fix:lint:eslint",
+        "fix:lint:stylelint"
+      ]
+    },
+    "fix:lint:eslint": {
+      "dependsOn": ["//#install:modules", "^build"],
+      "cache": false
+    },
+    "fix:lint:stylelint": {
+      "dependsOn": ["//#install:modules"],
+      "cache": false
+    },
+    "start:prod": {
+      "dependsOn": ["//#install:modules", "build"]
+    },
+    "test:lint": {
+      "dependsOn": ["test:lint:eslint", "test:lint:stylelint"]
+    },
+    "test:lint:eslint": {
+      "dependsOn": ["//#install:modules", "^build"]
+    },
+    "test:lint:stylelint": {
+      "dependsOn": ["//#install:modules"]
+    },
+    "test:types": {
+      "dependsOn": ["//#install:modules", "^build", "build"]
+    }
+  }
+}

+ 4 - 0
apps/developer-hub/vercel.json

@@ -0,0 +1,4 @@
+{
+  "$schema": "https://openapi.vercel.sh/vercel.json",
+  "buildCommand": "turbo run build:vercel --filter @pythnetwork/developer-hub"
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 406 - 88
pnpm-lock.yaml


+ 6 - 0
pnpm-workspace.yaml

@@ -82,8 +82,10 @@ catalog:
   "@storybook/react": ^8.6.12
   "@svgr/webpack": ^8.1.0
   "@tailwindcss/forms": ^0.5.10
+  "@tailwindcss/postcss": "^4.1.6"
   "@tanstack/react-query": ^5.71.5
   "@types/jest": ^29.5.14
+  "@types/mdx": "^2.0.13"
   "@types/node": ^22.14.0
   "@types/react": ^19.1.0
   "@types/react-dom": ^19.1.1
@@ -101,6 +103,9 @@ catalog:
   dnum: ^2.14.0
   eslint: ^9.23.0
   framer-motion: ^12.6.3
+  fumadocs-core: "^15.3.0"
+  fumadocs-mdx: "^11.6.3"
+  fumadocs-ui: "^15.3.0"
   highlight.js: ^11.11.1
   ip-range-check: ^0.2.0
   jest: ^29.7.0
@@ -153,6 +158,7 @@ onlyBuiltDependencies:
   - "@injectivelabs/utils"
   - "@parcel/watcher"
   - "@scarf/scarf"
+  - "@tailwindcss/oxide"
   - "@trufflesuite/bigint-buffer"
   - bigint-buffer
   - blake-hash

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä