Uh oh!
diff --git a/apps/staking/src/components/Header/index.tsx b/apps/staking/src/components/Header/index.tsx
index 92d1e184cc..77cc549708 100644
--- a/apps/staking/src/components/Header/index.tsx
+++ b/apps/staking/src/components/Header/index.tsx
@@ -1,5 +1,3 @@
-"use client";
-
import clsx from "clsx";
import type { HTMLAttributes } from "react";
diff --git a/apps/staking/src/components/NotFound/index.tsx b/apps/staking/src/components/NotFound/index.tsx
index c7aa5012e6..21147010d1 100644
--- a/apps/staking/src/components/NotFound/index.tsx
+++ b/apps/staking/src/components/NotFound/index.tsx
@@ -1,7 +1,7 @@
import { LinkButton } from "../Button";
export const NotFound = () => (
-
+
Not Found
diff --git a/apps/staking/src/components/WalletButton/index.tsx b/apps/staking/src/components/WalletButton/index.tsx
index 3eb729c332..6c27f1fe09 100644
--- a/apps/staking/src/components/WalletButton/index.tsx
+++ b/apps/staking/src/components/WalletButton/index.tsx
@@ -14,6 +14,7 @@ import { useWallet } from "@solana/wallet-adapter-react";
import { useWalletModal } from "@solana/wallet-adapter-react-ui";
import type { PublicKey } from "@solana/web3.js";
import clsx from "clsx";
+import { useSelectedLayoutSegment } from "next/navigation";
import {
type ComponentProps,
type ComponentType,
@@ -33,6 +34,7 @@ import {
SubmenuTrigger,
} from "react-aria-components";
+import { BLOCKED_SEGMENT } from "../../config/isomorphic";
import {
StateType as ApiStateType,
type States,
@@ -47,6 +49,12 @@ import { ModalDialog } from "../ModalDialog";
type Props = Omit, "onClick" | "children">;
export const WalletButton = (props: Props) => {
+ const segment = useSelectedLayoutSegment();
+ // eslint-disable-next-line unicorn/no-null
+ return segment === BLOCKED_SEGMENT ? null : ;
+};
+
+const WalletButtonImpl = (props: Props) => {
const api = useApi();
switch (api.type) {
diff --git a/apps/staking/src/config/isomorphic.ts b/apps/staking/src/config/isomorphic.ts
index db2626f0dc..935e0a2221 100644
--- a/apps/staking/src/config/isomorphic.ts
+++ b/apps/staking/src/config/isomorphic.ts
@@ -11,3 +11,13 @@
* with the optimized React build, etc.
*/
export const IS_PRODUCTION_BUILD = process.env.NODE_ENV === "production";
+
+/**
+ * Region-blocked requests will be redirected here. This is used in the
+ * middleware to implement the block, and also consumed in any components that
+ * are part of the page layout but need to know if the request is blocked from
+ * accessing the app, such as the WalletButton in the app header.
+ *
+ * Don't change unless you also change the relevant app route path to match.
+ */
+export const BLOCKED_SEGMENT = "blocked";
diff --git a/apps/staking/src/config/server.ts b/apps/staking/src/config/server.ts
index 4643f40a4d..b3f58c9ee9 100644
--- a/apps/staking/src/config/server.ts
+++ b/apps/staking/src/config/server.ts
@@ -36,6 +36,13 @@ export const WALLETCONNECT_PROJECT_ID = demandInProduction(
);
export const RPC = process.env.RPC;
export const IS_MAINNET = process.env.IS_MAINNET !== undefined;
+export const BLOCKED_REGIONS =
+ process.env.BLOCKED_REGIONS === undefined ||
+ process.env.BLOCKED_REGIONS === ""
+ ? []
+ : process.env.BLOCKED_REGIONS.split(",").map((region) =>
+ region.toLowerCase().trim(),
+ );
class MissingEnvironmentError extends Error {
constructor(name: string) {
diff --git a/apps/staking/src/middleware.ts b/apps/staking/src/middleware.ts
new file mode 100644
index 0000000000..a51d0acc1e
--- /dev/null
+++ b/apps/staking/src/middleware.ts
@@ -0,0 +1,24 @@
+import { type NextRequest, NextResponse } from "next/server";
+
+import { BLOCKED_SEGMENT } from "./config/isomorphic";
+import { BLOCKED_REGIONS } from "./config/server";
+
+export const middleware = (request: NextRequest) => {
+ if (blockRequest(request)) {
+ return NextResponse.rewrite(new URL(`/${BLOCKED_SEGMENT}`, request.url));
+ } else if (request.nextUrl.pathname.startsWith("/blocked")) {
+ return NextResponse.rewrite(new URL("/not-found", request.url));
+ } else {
+ return;
+ }
+};
+
+const blockRequest = (request: NextRequest) =>
+ request.geo?.country !== undefined &&
+ BLOCKED_REGIONS.includes(request.geo.country.toLowerCase());
+
+export const config = {
+ matcher: [
+ "/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
+ ],
+};