Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
402 changes: 402 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test": "test-storybook",
"test:unit": "vitest run",
"coverage": "test-storybook --coverage",
"chromatic": "npx chromatic"
},
Expand Down Expand Up @@ -63,11 +64,13 @@
"async-mutex": "^0.5.0",
"blockstore-core": "^5.0.2",
"buffer": "^6.0.3",
"canonify": "^2.1.1",
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canonify is added as a dependency but isn’t referenced anywhere in the repo. Please remove it to avoid unnecessary supply-chain surface area (or use it if canonical JSON is actually required).

Suggested change
"canonify": "^2.1.1",

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@bwmx bwmx Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correct, package-lock.json will need updated also.

"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0",
"lucide-react": "^0.475.0",
"lute-connect": "^1.4.1",
"marked": "^17.0.3",
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

marked@^17 declares Node >= 20 in its engines, but this repo’s docs/CI still support Node 18. This can break Node 18 builds at install/runtime. Either pin marked to a Node-18-compatible version or formally bump the project’s supported Node version and update CI/docs.

Suggested change
"marked": "^17.0.3",
"marked": "^16.0.0",

Copilot uses AI. Check for mistakes.
"multiformats": "^13.3.1",
"nanostores": "^0.10.3",
"p-map": "^7.0.3",
Expand All @@ -79,6 +82,7 @@
"react-router-dom": "^6.27.0",
"tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7",
"tweetnacl": "^1.0.3",
"typescript": "^5.6.3",
"zod": "^3.24.1"
},
Expand Down Expand Up @@ -112,6 +116,7 @@
"tsx": "^4.19.3",
"vite-plugin-pwa": "^1.0.0",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^4.0.18",
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vitest@^4 declares Node >= 20 in its engines, but this repo supports Node 18 (docs + CI matrix). That means npm run test:unit won’t work on Node 18 and may cause install warnings/failures depending on npm settings. Consider pinning a Node-18-compatible Vitest version or updating the project’s Node support policy.

Suggested change
"vitest": "^4.0.18",
"vitest": "^3.0.0",

Copilot uses AI. Check for mistakes.
"yargs": "^18.0.0"
},
"peerDependencies": {
Expand Down
5 changes: 3 additions & 2 deletions src/components/ProfileCard/ProfileCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { cn } from "@/functions";
import { XGovProposerStatusPill } from "../XGovProposerStatusPill/XGovProposerStatusPill";
import { XGovStatusPill } from "../XGovStatusPill/XGovStatusPill";
import { useState } from "react";
import termsAndConditionsString from "./TermsAndConditionsText.md?raw";
import { TermsAndConditionsModal } from "@/recipes";
import { useTerms } from "@/hooks";
import { TestnetDispenserBanner } from "../TestnetDispenserBanner/TestnetDispenserBanner";
import { BecomeProposerModal } from "../BecomeProposerModal/BecomeProposerModal";
import { BecomeXGovModal } from "../BecomeXGovModal/BecomeXGovModal";
Expand Down Expand Up @@ -59,6 +59,7 @@ export function ProfileCard({
className = "",
}: ProfileCardProps) {
const delegates = useXGovDelegates(address);
const terms = useTerms();
const [showBecomeXGovModal, setShowBecomeXGovModal] = useState(false);
const [showBecomeProposerModal, setShowBecomeProposerModal] = useState(false);
const [showBecomeProposerTermsModal, setShowBecomeProposerTermsModal] = useState(false);
Expand Down Expand Up @@ -219,7 +220,7 @@ export function ProfileCard({
</span>
</>
}
terms={termsAndConditionsString}
terms={terms.data?.content ?? ""}
isOpen={showBecomeProposerTermsModal}
onClose={() => setShowBecomeProposerTermsModal(false)}
Comment on lines +223 to 225
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing terms.data?.content ?? "" can open the Terms modal with an empty string while the query is still loading or if it errors. Because the modal requires scrolling to enable “Accept”, users can get stuck on a blank/disabled modal. Block opening until terms are loaded, show a loading/error state, or fall back to bundled static terms.

Copilot uses AI. Check for mistakes.
onAccept={() => {
Expand Down
39 changes: 35 additions & 4 deletions src/components/TermsAndConditionsView/TermsAndConditionsView.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
import { formatMarkdownToHtml } from "@/recipes";
import termsAndConditionsString from "../ProfileCard/TermsAndConditionsText.md?raw";
import { renderTermsMarkdown } from "@/lib/markdown";
import { useTerms } from "@/hooks";
import { UseQuery } from "@/hooks/useQuery";

interface TermsAndConditionsViewProps {
title?: string;
description?: string | JSX.Element;
className?: string;
}

export function TermsAndConditionsView({
function TermsAndConditionsContent({
title = "xGov Proposer Terms & Conditions",
description,
className = "",
}: TermsAndConditionsViewProps) {
const formattedContent = formatMarkdownToHtml(termsAndConditionsString);
const terms = useTerms();

if (terms.isLoading) {
return (
<div className={`w-full max-w-6xl mx-auto p-4 ${className}`}>
<div className="text-center text-gray-500 dark:text-gray-400 py-12">
Loading terms...
</div>
</div>
);
}

if (terms.isError) {
return (
<div className={`w-full max-w-6xl mx-auto p-4 ${className}`}>
<div className="text-center text-red-500 py-12">
Failed to load terms and conditions.
</div>
</div>
);
}

const formattedContent = renderTermsMarkdown(terms.data?.content ?? "");

return (
<div className={`w-full max-w-6xl mx-auto p-4 ${className}`}>
Expand All @@ -38,3 +61,11 @@ export function TermsAndConditionsView({
</div>
);
}

export function TermsAndConditionsView(props: TermsAndConditionsViewProps) {
return (
<UseQuery>
<TermsAndConditionsContent {...props} />
</UseQuery>
);
}
5 changes: 3 additions & 2 deletions src/components/TutorialDialog/TutorialDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import { Button } from "@/components/ui/button";
import { CheckIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import { cn } from "@/functions";
import { TermsAndConditionsModal } from "@/recipes";
import termsAndConditionsString from "../ProfileCard/TermsAndConditionsText.md?raw";
import { BecomeProposerModal } from "../BecomeProposerModal/BecomeProposerModal";
import { BecomeXGovModal } from "../BecomeXGovModal/BecomeXGovModal";
import type { TransactionStateInfo } from "@/api/types/transaction_state";
import { CheckCircleIcon } from "../icons/CheckCircleIcon";
import { useTerms } from "@/hooks";

export interface TutorialDialogProps {
isOpen: boolean;
Expand Down Expand Up @@ -110,6 +110,7 @@ export function TutorialDialog({
isXGov = false,
isProposer = false
}: TutorialDialogProps) {
const terms = useTerms();
const [currentStep, setCurrentStep] = useState(0);
const [showBecomeXGovModal, setShowBecomeXGovModal] = useState(false);
const [showBecomeProposerModal, setShowBecomeProposerModal] = useState(false);
Expand Down Expand Up @@ -448,7 +449,7 @@ export function TutorialDialog({
</div>
</>
}
terms={termsAndConditionsString}
terms={terms.data?.content ?? ""}
isOpen={showBecomeProposerTermsModal}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing terms.data?.content ?? "" can open the Terms modal with an empty string while the query is still loading or if it errors. Because the modal requires scrolling to enable “Accept”, users can get stuck on a blank/disabled modal. Block opening until terms are loaded, show a loading/error state, or fall back to bundled static terms.

Suggested change
isOpen={showBecomeProposerTermsModal}
isOpen={showBecomeProposerTermsModal && !!terms.data?.content}

Copilot uses AI. Check for mistakes.
onClose={() => setShowBecomeProposerTermsModal(false)}
onAccept={() => {
Expand Down
13 changes: 13 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

type R2Bucket = import("@cloudflare/workers-types").R2Bucket;

declare namespace App {
interface Locals {
runtime?: {
env: {
BUCKET?: R2Bucket;
[key: string]: unknown;
};
};
}
}
1 change: 1 addition & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export * from "./useQuery.tsx";
export * from "./useRegistry.ts";
export * from "./useSearchParams.ts";
export * from "./useTimeLeft.ts";
export * from "./useTerms.ts";
export * from "./useWallet.tsx";
23 changes: 23 additions & 0 deletions src/hooks/useTerms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useQuery } from "@tanstack/react-query";

interface TermsData {
content: string;
source: "r2" | "static";
version: number;
}

async function fetchTerms(): Promise<TermsData> {
const res = await fetch("/api/terms");
if (!res.ok) {
throw new Error("Failed to fetch terms");
}
return res.json();
}

export function useTerms() {
return useQuery({
queryKey: ["terms"],
queryFn: fetchTerms,
staleTime: 5 * 60 * 1000,
});
}
Loading