Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds a Markee-powered “Gardens Sign” UI to the Gardens landing page, fetching the current top Markee message from a subgraph and allowing users to submit a new message via an on-chain transaction.
Changes:
- Introduces
MarkeeSigncomponent to fetch/display the current Markee message + top amount. - Introduces
MarkeeModalcomponent to submit a new Markee message viacreateMarkeeon Base. - Embeds
MarkeeSigninto the Gardens client page header and adjusts spacing.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 11 comments.
| File | Description |
|---|---|
| apps/web/components/MarkeeSign.tsx | New sign UI; fetches Markee data from The Graph and opens the edit modal. |
| apps/web/components/MarkeeModal.tsx | New modal for composing/submitting a message and sending a contract transaction. |
| apps/web/app/(app)/gardens/client-page.tsx | Adds the sign to the header and tweaks layout/imports. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const validate = () => { | ||
| if (!message.trim()) { | ||
| setInputError("Message cannot be empty."); | ||
| return false; | ||
| } | ||
| if (!ethAmount || isNaN(parseFloat(ethAmount)) || parseFloat(ethAmount) <= 0) { | ||
| setInputError("Enter a valid ETH amount."); | ||
| return false; | ||
| } | ||
| if (minBeat > BigInt(0) && parseEther(ethAmount) < minBeat) { | ||
| setInputError( | ||
| `You need at least ${minBeatEth.toFixed(4)} ETH to become top dawg.`, | ||
| ); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
validate() calls parseEther(ethAmount) without guarding for exceptions. parseEther throws on invalid/over-precise inputs (e.g. too many decimal places), which would crash the modal because this happens outside the try/catch in handleSubmit. Wrap the parseEther conversion in try/catch inside validate() and return a friendly input error when parsing fails.
apps/web/components/MarkeeModal.tsx
Outdated
| // wagmi v1 API | ||
| const { writeAsync, isLoading: isPending } = useContractWrite({ | ||
| address: strategyAddress, | ||
| abi: TOP_DAWG_PARTNER_ABI, | ||
| functionName: "createMarkee", | ||
| chainId: base.id, | ||
| }); | ||
|
|
||
| const publicClient = usePublicClient({ chainId: base.id }); |
There was a problem hiding this comment.
The app largely standardizes contract writes through useContractWriteWithConfirmations (adds chain-aware confirmations, simulation/toasts, and consistent error handling). Using raw useContractWrite + manual waitForTransactionReceipt here bypasses those behaviors and creates an inconsistent UX. Consider migrating this modal to useContractWriteWithConfirmations for consistency with the rest of the codebase (see apps/web/hooks/useContractWriteWithConfirmations.ts).
| } catch (err: any) { | ||
| setIsConfirming(false); | ||
| if (!err?.message?.includes("User rejected")) { | ||
| setInputError("Transaction failed. Please try again."); | ||
| } | ||
| } |
There was a problem hiding this comment.
Error handling checks err.message.includes("User rejected"), which is brittle and may miss other wallet/provider messages. The codebase already handles this robustly via error.cause instanceof UserRejectedRequestError (see apps/web/utils/transactionMessages.ts). Consider switching to that approach (or relying on useContractWriteWithConfirmations notifications) so user-rejection is detected consistently.
| import MarkeeSign from "@/components/MarkeeSign"; | ||
| import { LightCommunity } from "@/components/Communities"; |
There was a problem hiding this comment.
Import order likely violates the repo’s import/order + alphabetize rule: @/components/Communities should come before @/components/MarkeeSign within the internal group. Reorder these imports to keep lint passing.
| import MarkeeSign from "@/components/MarkeeSign"; | |
| import { LightCommunity } from "@/components/Communities"; | |
| import { LightCommunity } from "@/components/Communities"; | |
| import MarkeeSign from "@/components/MarkeeSign"; |
apps/web/components/MarkeeSign.tsx
Outdated
| const MARKEE_SUBGRAPH = | ||
| "https://gateway.thegraph.com/api/subgraphs/id/8kMCKUHSY7o6sQbsvufeLVo8PifxrsnagjVTMGcs6KdF"; |
There was a problem hiding this comment.
MARKEE_SUBGRAPH is hard-coded to The Graph gateway without incorporating the configured gateway key pattern used elsewhere (see apps/web/configs/chains.tsx getGatewayKey). If the gateway requires an API key (or changes), this client-side fetch will fail in production. Consider building this URL from NEXT_PUBLIC_SUBGRAPH_KEY (or routing via a Next.js API route) so the endpoint is environment-configurable and consistent with the rest of the app.
| const MARKEE_SUBGRAPH = | |
| "https://gateway.thegraph.com/api/subgraphs/id/8kMCKUHSY7o6sQbsvufeLVo8PifxrsnagjVTMGcs6KdF"; | |
| const MARKEE_SUBGRAPH_ID = "8kMCKUHSY7o6sQbsvufeLVo8PifxrsnagjVTMGcs6KdF"; | |
| const SUBGRAPH_GATEWAY_KEY = process.env.NEXT_PUBLIC_SUBGRAPH_KEY; | |
| const DEFAULT_MARKEE_SUBGRAPH = `https://gateway.thegraph.com/api/subgraphs/id/${MARKEE_SUBGRAPH_ID}`; | |
| const MARKEE_SUBGRAPH = SUBGRAPH_GATEWAY_KEY | |
| ? `https://gateway.thegraph.com/api/${SUBGRAPH_GATEWAY_KEY}/subgraphs/id/${MARKEE_SUBGRAPH_ID}` | |
| : DEFAULT_MARKEE_SUBGRAPH; |
apps/web/components/MarkeeSign.tsx
Outdated
| <button | ||
| onClick={() => setModalOpen(true)} | ||
| className="group relative mx-auto mt-6 cursor-pointer" | ||
| aria-label="Click to edit this Markee sign" | ||
| > | ||
| {/* Sign body */} | ||
| <div className="border border-neutral-content/30 rounded px-10 py-5 min-w-[280px] max-w-sm bg-neutral hover:border-primary-content/50 transition-colors duration-200"> | ||
| <p className="font-mono text-neutral-content text-base group-hover:text-primary-content transition-colors duration-200 text-center leading-snug"> | ||
| {data ? data.message : "loading..."} |
There was a problem hiding this comment.
The sign button can open the modal while data is still null (or after a fetch failure), but MarkeeModal then receives minimumPrice=0 / currentTopDawg=0, which can allow entering an amount that will revert on-chain. Consider disabling the button (or the submit action) until the subgraph data has loaded successfully, and/or surfacing a loading/error state in the modal.
apps/web/components/MarkeeModal.tsx
Outdated
| const minBeat = | ||
| currentTopDawg > BigInt(0) ? currentTopDawg + BigInt(1) : minimumPrice; | ||
| const minBeatEth = parseFloat(formatEther(minBeat)); | ||
|
|
||
| const validate = () => { | ||
| if (!message.trim()) { | ||
| setInputError("Message cannot be empty."); | ||
| return false; | ||
| } | ||
| if (!ethAmount || isNaN(parseFloat(ethAmount)) || parseFloat(ethAmount) <= 0) { | ||
| setInputError("Enter a valid ETH amount."); | ||
| return false; | ||
| } | ||
| if (minBeat > BigInt(0) && parseEther(ethAmount) < minBeat) { | ||
| setInputError( | ||
| `You need at least ${minBeatEth.toFixed(4)} ETH to become top dawg.`, | ||
| ); |
There was a problem hiding this comment.
minBeat is currentTopDawg + 1 wei, but the UI and error message display minBeatEth.toFixed(4). For typical values, adding 1 wei won’t change the 4-decimal display, so the message can say “at least 1.0000 ETH” while actually requiring slightly more. Consider rounding the displayed minimum up to the UI’s input granularity (or increasing display precision / adjusting the min increment) so the error message matches the real requirement.
apps/web/components/MarkeeModal.tsx
Outdated
| try { | ||
| const { hash } = await writeAsync({ | ||
| args: [message.trim(), name.trim()], | ||
| value: parseEther(ethAmount), | ||
| }); | ||
|
|
||
| if (hash && publicClient) { | ||
| setIsConfirming(true); | ||
| await publicClient.waitForTransactionReceipt({ hash }); | ||
| setIsConfirming(false); | ||
| setIsSuccess(true); | ||
| onSuccess(); | ||
| } | ||
| } catch (err: any) { | ||
| setIsConfirming(false); | ||
| if (!err?.message?.includes("User rejected")) { | ||
| setInputError("Transaction failed. Please try again."); | ||
| } |
There was a problem hiding this comment.
If publicClient is undefined, a successful writeAsync will return a hash but the code will skip waiting for confirmations and never call onSuccess, leaving the modal in a confusing state. Handle the publicClient-missing case explicitly (e.g., show an error) or switch to useWaitForTransaction / the existing useContractWriteWithConfirmations hook so success is tracked reliably.
| try { | |
| const { hash } = await writeAsync({ | |
| args: [message.trim(), name.trim()], | |
| value: parseEther(ethAmount), | |
| }); | |
| if (hash && publicClient) { | |
| setIsConfirming(true); | |
| await publicClient.waitForTransactionReceipt({ hash }); | |
| setIsConfirming(false); | |
| setIsSuccess(true); | |
| onSuccess(); | |
| } | |
| } catch (err: any) { | |
| setIsConfirming(false); | |
| if (!err?.message?.includes("User rejected")) { | |
| setInputError("Transaction failed. Please try again."); | |
| } | |
| if (!publicClient) { | |
| setInputError("Unable to connect to the network. Please refresh the page and try again."); | |
| return; | |
| } | |
| setIsConfirming(true); | |
| try { | |
| const { hash } = await writeAsync({ | |
| args: [message.trim(), name.trim()], | |
| value: parseEther(ethAmount), | |
| }); | |
| if (hash) { | |
| await publicClient.waitForTransactionReceipt({ hash }); | |
| setIsSuccess(true); | |
| onSuccess(); | |
| } else { | |
| setInputError("Transaction hash was not returned. Please try again."); | |
| } | |
| } catch (err: any) { | |
| if (!err?.message?.includes("User rejected")) { | |
| setInputError("Transaction failed. Please try again."); | |
| } | |
| } finally { | |
| setIsConfirming(false); |
apps/web/components/MarkeeModal.tsx
Outdated
| {/* Success state */} | ||
| {isSuccess && ( | ||
| <div className="mb-4 rounded border border-green-500/30 bg-green-500/10 px-3 py-2 text-sm text-green-400"> | ||
| 🎉 Your message is live on the Gardens sign! | ||
| </div> | ||
| )} | ||
|
|
There was a problem hiding this comment.
isSuccess/success banner state is effectively unreachable because onSuccess() immediately closes/unmounts the modal in the parent. Either remove the success UI state, or delay closing so the user can see confirmation feedback (or use the app’s transaction toast notifications).
| {/* Success state */} | |
| {isSuccess && ( | |
| <div className="mb-4 rounded border border-green-500/30 bg-green-500/10 px-3 py-2 text-sm text-green-400"> | |
| 🎉 Your message is live on the Gardens sign! | |
| </div> | |
| )} |
apps/web/components/MarkeeSign.tsx
Outdated
| fetch(MARKEE_SUBGRAPH, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ query: QUERY }), | ||
| }) | ||
| .then((r) => r.json()) | ||
| .then((res) => { | ||
| const markee = res.data?.markees?.[0]; | ||
| const strategy = res.data?.topDawgPartnerStrategy; | ||
| setData({ | ||
| message: markee?.currentMessage ?? "this is a sign.", | ||
| totalFundsAdded: BigInt(markee?.totalFundsAdded ?? 0), | ||
| minimumPrice: BigInt(strategy?.minimumPrice ?? 0), | ||
| markeeId: markee?.id ?? null, | ||
| }); | ||
| }) |
There was a problem hiding this comment.
Neither fetch path checks response.ok nor handles GraphQL errors in the JSON payload. This can lead to setData being populated with defaults even when the query failed or returned errors. Please check r.ok and/or res.errors and treat those as errors (showing a fallback state) rather than silently continuing.
This pull request introduces a new interactive "Markee sign" feature to the Gardens client page, allowing users to view and update a message displayed on the page by paying ETH. The feature integrates with the Markee protocol, fetches current sign data from a subgraph, and provides a modal for users to submit a new message. Minor code cleanups were also made.
Markee sign feature integration:
MarkeeSigncomponent to the Gardens client page, displaying the current sign message and ETH badge, and allowing users to open a modal to update the sign. (apps/web/app/(app)/gardens/client-page.tsx, [1] [2]MarkeeSigncomponent, which fetches the current sign message, top dawg ETH amount, and minimum price from the Markee subgraph, displays them, and handles modal opening/closing and data refresh after successful updates. (apps/web/components/MarkeeSign.tsx, apps/web/components/MarkeeSign.tsxR1-R126)MarkeeModalcomponent, providing a form for users to submit a new sign message and ETH payment, with validation, transaction handling, and success/error feedback. (apps/web/components/MarkeeModal.tsx, apps/web/components/MarkeeModal.tsxR1-R270)Code cleanup:
apps/web/app/(app)/gardens/client-page.tsx, [1] [2]