From 8c76a6748990e6571d70bed46afefa433c4bd762 Mon Sep 17 00:00:00 2001
From: Joe <98909677+moonclavedev@users.noreply.github.com>
Date: Sun, 30 Mar 2025 22:16:26 -0700
Subject: [PATCH 1/2] [ux] Experiment network selection as global state
---
astro.config.mjs | 4 +-
src/components/NetworkProvider.astro | 31 +++++++++++++
src/components/NetworkSelect.astro | 66 ++++++++++++++++++++++++++++
src/config/networks.ts | 3 ++
src/globals.css | 1 +
src/middlewares/network-redirect.ts | 42 ++++++++++++++++++
src/starlight-overrides/Head.astro | 2 +
src/starlight-overrides/Header.astro | 2 +
src/styles/network-select.css | 24 ++++++++++
src/vercel-middleware.ts | 2 +
10 files changed, 176 insertions(+), 1 deletion(-)
create mode 100644 src/components/NetworkProvider.astro
create mode 100644 src/components/NetworkSelect.astro
create mode 100644 src/config/networks.ts
create mode 100644 src/middlewares/network-redirect.ts
create mode 100644 src/styles/network-select.css
diff --git a/astro.config.mjs b/astro.config.mjs
index ace34cb6..a4d1669c 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -13,6 +13,7 @@ import sitemap from "@astrojs/sitemap";
import partytown from "@astrojs/partytown";
import node from "@astrojs/node";
import react from "@astrojs/react";
+// Removed astro-iconify import
import Icons from "unplugin-icons/vite";
import starlightLlmsTxt from "starlight-llms-txt";
import { sidebar } from "./astro.sidebar.ts";
@@ -95,7 +96,7 @@ export default defineConfig({
},
components: {
Head: "./src/starlight-overrides/Head.astro",
- // Header: "./src/starlight-overrides/Header.astro",
+ Header: "./src/starlight-overrides/Header.astro",
LanguageSelect: "./src/starlight-overrides/LanguageSelect.astro",
MobileMenuToggle: "./src/starlight-overrides/MobileMenuToggle.astro",
PageFrame: "./src/starlight-overrides/PageFrame.astro",
@@ -161,6 +162,7 @@ export default defineConfig({
experimentalReactChildren: true,
include: ["**/GraphQLEditor.tsx"],
}),
+ // Removed astro-iconify integration
],
adapter: process.env.VERCEL
? vercel({
diff --git a/src/components/NetworkProvider.astro b/src/components/NetworkProvider.astro
new file mode 100644
index 00000000..c1cd47bb
--- /dev/null
+++ b/src/components/NetworkProvider.astro
@@ -0,0 +1,31 @@
+---
+import { DEFAULT_NETWORK } from "../config/networks";
+---
+
+
diff --git a/src/components/NetworkSelect.astro b/src/components/NetworkSelect.astro
new file mode 100644
index 00000000..6d137fdc
--- /dev/null
+++ b/src/components/NetworkSelect.astro
@@ -0,0 +1,66 @@
+---
+import Select from "@astrojs/starlight/components/Select.astro";
+import { MOVE_REFERENCE_BRANCHES } from "../content.config";
+import { DEFAULT_NETWORK } from "../config/networks";
+
+const networks = MOVE_REFERENCE_BRANCHES.map(network => ({
+ label: network.label,
+ value: network.name,
+ selected: network.name === DEFAULT_NETWORK
+}));
+---
+
+
+
+
+
+{/* Inlined to avoid FOUC. Uses global scope from `NetworkProvider.astro` */}
+
+
+
diff --git a/src/config/networks.ts b/src/config/networks.ts
new file mode 100644
index 00000000..a87d1b8b
--- /dev/null
+++ b/src/config/networks.ts
@@ -0,0 +1,3 @@
+export const NETWORK_NAMES = ["mainnet", "testnet", "devnet"] as const;
+export const DEFAULT_NETWORK = "mainnet";
+export type NetworkName = (typeof NETWORK_NAMES)[number];
diff --git a/src/globals.css b/src/globals.css
index 72eaa452..1ad363ba 100644
--- a/src/globals.css
+++ b/src/globals.css
@@ -1,6 +1,7 @@
@import "tailwindcss";
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
@plugin "@astrojs/starlight-tailwind";
+@import "./styles/network-select.css";
/* Register your custom font family and tell the browser where to find it. */
@font-face {
diff --git a/src/middlewares/network-redirect.ts b/src/middlewares/network-redirect.ts
new file mode 100644
index 00000000..c6e7bfdf
--- /dev/null
+++ b/src/middlewares/network-redirect.ts
@@ -0,0 +1,42 @@
+import { NETWORK_NAMES, DEFAULT_NETWORK, type NetworkName } from "../config/networks";
+
+// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
+export default function middleware(request: Request): Response | void {
+ const url = new URL(request.url);
+ const pathname = url.pathname;
+
+ // Check for cookie
+ const cookies = request.headers.get("cookie") ?? "";
+ const networkCookieMatch = /preferred_network=([a-z-]+)/.exec(cookies);
+ let preferredNetwork: NetworkName = DEFAULT_NETWORK;
+
+ // Validate that the cookie value is a valid network
+ if (networkCookieMatch && NETWORK_NAMES.includes(networkCookieMatch[1] as NetworkName)) {
+ preferredNetwork = networkCookieMatch[1] as NetworkName;
+ }
+
+ // Only process move-reference paths
+ if (!pathname.startsWith("/move-reference")) {
+ return undefined;
+ }
+
+ // Check if the path already includes a network
+ const networkPathMatch = /^\/move-reference\/([a-z-]+)(\/.*|$)/.exec(pathname);
+
+ if (networkPathMatch) {
+ const pathNetwork = networkPathMatch[1];
+ const remainingPath = networkPathMatch[2] ?? "/";
+
+ // If the network in the path is valid but different from the preferred one, redirect
+ if (NETWORK_NAMES.includes(pathNetwork as NetworkName) && pathNetwork !== preferredNetwork) {
+ url.pathname = `/move-reference/${preferredNetwork}${remainingPath}`;
+ return Response.redirect(url);
+ }
+ } else if (pathname === "/move-reference" || pathname === "/move-reference/") {
+ // If no network is specified in the path, redirect to the preferred network
+ url.pathname = `/move-reference/${preferredNetwork}/`;
+ return Response.redirect(url);
+ }
+
+ return undefined;
+}
diff --git a/src/starlight-overrides/Head.astro b/src/starlight-overrides/Head.astro
index d51fdad5..b1bb5f12 100644
--- a/src/starlight-overrides/Head.astro
+++ b/src/starlight-overrides/Head.astro
@@ -3,6 +3,7 @@ import Default from "@astrojs/starlight/components/Head.astro";
// import { ClientRouter } from "astro:transitions";
import { Schema } from "astro-seo-schema";
import TextSizeProvider from "../components/TextSizeProvider.astro";
+import NetworkProvider from "../components/NetworkProvider.astro";
import { SUPPORTED_LANGUAGES } from "../config/locales";
import { getImageUrl } from "~/lib/og-image/getImageUrl";
@@ -167,6 +168,7 @@ const lastUpdatedDate = lastUpdated ? lastUpdated.toISOString() : new Date().toI
/>
+
diff --git a/src/starlight-overrides/Header.astro b/src/starlight-overrides/Header.astro
index 720cf115..d5b7ab09 100644
--- a/src/starlight-overrides/Header.astro
+++ b/src/starlight-overrides/Header.astro
@@ -4,6 +4,7 @@ import SiteTitle from "@astrojs/starlight/components/SiteTitle.astro";
import SocialIcons from "@astrojs/starlight/components/SocialIcons.astro";
import ThemeSelect from "@astrojs/starlight/components/ThemeSelect.astro";
// import TextSizeToggle from "../components/TextSizeToggle.astro";
+import NetworkSelect from "../components/NetworkSelect.astro";
import LanguageSelect from "./LanguageSelect.astro";
---
@@ -21,6 +22,7 @@ import LanguageSelect from "./LanguageSelect.astro";
+
diff --git a/src/styles/network-select.css b/src/styles/network-select.css
new file mode 100644
index 00000000..497e9195
--- /dev/null
+++ b/src/styles/network-select.css
@@ -0,0 +1,24 @@
+/* Network selector styles */
+:root[data-preferred-network="mainnet"] {
+ --network-color: var(--sl-color-green-high);
+}
+
+:root[data-preferred-network="testnet"] {
+ --network-color: var(--sl-color-orange-high);
+}
+
+:root[data-preferred-network="devnet"] {
+ --network-color: var(--sl-color-blue-high);
+}
+
+/* Add a subtle indicator to the network select */
+starlight-network-select::after {
+ content: "";
+ position: absolute;
+ bottom: -2px;
+ left: 0;
+ width: 100%;
+ height: 2px;
+ background-color: var(--network-color, transparent);
+ border-radius: 1px;
+}
diff --git a/src/vercel-middleware.ts b/src/vercel-middleware.ts
index 76955916..ff3081ea 100644
--- a/src/vercel-middleware.ts
+++ b/src/vercel-middleware.ts
@@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-invalid-void-type */
// Edge-compatible middleware that implements a middleware chain pattern
import i18nRedirect from "./middlewares/i18n-redirect";
+import networkRedirect from "./middlewares/network-redirect";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - auto-generated import
import { matcher } from "./middlewares/matcher-routes-dynamic";
@@ -30,6 +31,7 @@ async function applyMiddleware(
export default async function middleware(req: Request) {
return await applyMiddleware(req, [
i18nRedirect,
+ networkRedirect,
// Add more middleware functions here as needed
]);
}
From 00f0535bda1211a639261f50877b636407a7bd73 Mon Sep 17 00:00:00 2001
From: Joe <98909677+moonclavedev@users.noreply.github.com>
Date: Sun, 30 Mar 2025 23:05:39 -0700
Subject: [PATCH 2/2] [ux] Preferred Network intercept on i18n routes
---
src/middlewares/network-redirect.ts | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/src/middlewares/network-redirect.ts b/src/middlewares/network-redirect.ts
index c6e7bfdf..cda025fe 100644
--- a/src/middlewares/network-redirect.ts
+++ b/src/middlewares/network-redirect.ts
@@ -20,8 +20,17 @@ export default function middleware(request: Request): Response | void {
return undefined;
}
+ // Check for language prefix
+ const langPathMatch = /^\/([a-z]{2})(?:\/|$)/.exec(pathname);
+ // Ensure langPrefix is always a string
+ let langPrefix = "";
+ if (langPathMatch?.[1]) {
+ langPrefix = "/" + langPathMatch[1];
+ }
+ const pathWithoutLang = langPathMatch ? pathname.substring(langPathMatch[0].length) : pathname;
+
// Check if the path already includes a network
- const networkPathMatch = /^\/move-reference\/([a-z-]+)(\/.*|$)/.exec(pathname);
+ const networkPathMatch = /^\/move-reference\/([a-z-]+)(\/.*|$)/.exec(pathWithoutLang);
if (networkPathMatch) {
const pathNetwork = networkPathMatch[1];
@@ -29,12 +38,14 @@ export default function middleware(request: Request): Response | void {
// If the network in the path is valid but different from the preferred one, redirect
if (NETWORK_NAMES.includes(pathNetwork as NetworkName) && pathNetwork !== preferredNetwork) {
- url.pathname = `/move-reference/${preferredNetwork}${remainingPath}`;
+ // Construct the new path without template literals
+ url.pathname = langPrefix + "/move-reference/" + preferredNetwork + remainingPath;
return Response.redirect(url);
}
- } else if (pathname === "/move-reference" || pathname === "/move-reference/") {
+ } else if (pathWithoutLang === "/move-reference" || pathWithoutLang === "/move-reference/") {
// If no network is specified in the path, redirect to the preferred network
- url.pathname = `/move-reference/${preferredNetwork}/`;
+ // Construct the new path without template literals
+ url.pathname = langPrefix + "/move-reference/" + preferredNetwork + "/";
return Response.redirect(url);
}