Skip to content

Commit

Permalink
feat(staking): add region blocking
Browse files Browse the repository at this point in the history
  • Loading branch information
cprussin committed Sep 11, 2024
1 parent 55ec07a commit 9a1f68e
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 4 deletions.
1 change: 1 addition & 0 deletions apps/staking/src/app/blocked/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Blocked as default } from "../../components/Blocked";
19 changes: 19 additions & 0 deletions apps/staking/src/components/Blocked/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { LinkButton } from "../Button";

export const Blocked = () => (
<main className="grid size-full place-content-center py-20 text-center">
<h1 className="mb-8 text-4xl font-semibold text-pythpurple-400">
{"We're sorry"}
</h1>
<p className="mb-20 text-lg">
This program is currently unavailable to users in your region
</p>
<LinkButton
className="place-self-center px-24 py-3"
href="https://www.pyth.network"
target="_blank"
>
Read More About Pyth
</LinkButton>
</main>
);
2 changes: 1 addition & 1 deletion apps/staking/src/components/Error/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const Error = ({ error, reset }: Props) => {
}, [error, logger]);

return (
<main className="flex size-full flex-col items-center justify-center gap-2 text-center">
<main className="flex size-full flex-col items-center justify-center gap-2 py-20 text-center">
<div className="mb-10 flex flex-row items-center gap-6">
<ExclamationTriangleIcon className="size-16 text-red-700" />
<h1 className="text-3xl font-light">Uh oh!</h1>
Expand Down
2 changes: 0 additions & 2 deletions apps/staking/src/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
"use client";

import clsx from "clsx";
import type { HTMLAttributes } from "react";

Expand Down
2 changes: 1 addition & 1 deletion apps/staking/src/components/NotFound/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LinkButton } from "../Button";

export const NotFound = () => (
<main className="grid size-full place-content-center text-center">
<main className="grid size-full place-content-center py-20 text-center">
<h1 className="mb-8 text-4xl font-semibold text-pythpurple-400">
Not Found
</h1>
Expand Down
8 changes: 8 additions & 0 deletions apps/staking/src/components/WalletButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -33,6 +34,7 @@ import {
SubmenuTrigger,
} from "react-aria-components";

import { BLOCKED_SEGMENT } from "../../config/isomorphic";
import {
StateType as ApiStateType,
type States,
Expand All @@ -47,6 +49,12 @@ import { ModalDialog } from "../ModalDialog";
type Props = Omit<ComponentProps<typeof Button>, "onClick" | "children">;

export const WalletButton = (props: Props) => {
const segment = useSelectedLayoutSegment();
// eslint-disable-next-line unicorn/no-null
return segment === BLOCKED_SEGMENT ? null : <WalletButtonImpl {...props} />;
};

const WalletButtonImpl = (props: Props) => {
const api = useApi();

switch (api.type) {
Expand Down
10 changes: 10 additions & 0 deletions apps/staking/src/config/isomorphic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
7 changes: 7 additions & 0 deletions apps/staking/src/config/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
24 changes: 24 additions & 0 deletions apps/staking/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -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).*)",
],
};

0 comments on commit 9a1f68e

Please sign in to comment.