Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .cursorignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.env
.env.local
.env.production
*.pem
*.key
secrets/
4 changes: 2 additions & 2 deletions packages/backend/src/admin/dto/analytics-report.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { Transform } from 'class-transformer';
export class AnalyticsReportDto {
@ApiPropertyOptional({
description: 'Start date (inclusive)',
example: '2026-01-01',
example: '2026-01-29',
})
@IsOptional()
@IsDateString()
startDate?: string;

@ApiPropertyOptional({
description: 'End date (inclusive)',
example: '2026-02-01',
example: '2027-01-29',
})
@IsOptional()
@IsDateString()
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/components/Batch/BatchContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ export default function BatchContainer() {
tokenSymbol: token.symbol,
};
}),
[selectedBatchItems, getToken],
[selectedBatchItems, getToken, network],
);

const handleCloseDrawer = useCallback(() => {
Expand Down
15 changes: 2 additions & 13 deletions packages/nextjs/components/Transfer/TokenSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React, { useEffect, useRef, useState } from "react";
import Image from "next/image";
import { ResolvedToken } from "@polypay/shared";
import { useNetworkTokens } from "~~/hooks/app/useNetworkTokens";
import { useClickOutside } from "~~/hooks/useClickOutside";

interface TokenSelectorProps {
selectedToken: ResolvedToken;
Expand Down Expand Up @@ -31,19 +32,7 @@ export function TokenSelector({ selectedToken, onSelect, disabled = false }: Tok
}
}, [isOpen]);

// Close popover when clicking outside
useEffect(() => {
if (!isOpen) return;

const handleClickOutside = (event: MouseEvent) => {
if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [isOpen]);
useClickOutside(containerRef, () => setIsOpen(false), { isActive: isOpen });

const handleTokenSelect = (token: ResolvedToken) => {
onSelect(token);
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/components/Transfer/TransferContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useNetworkTokens } from "~~/hooks/app/useNetworkTokens";
import { useTokenBalances } from "~~/hooks/app/useTokenBalance";
import { useZodForm } from "~~/hooks/form";
import { TransferFormData, transferSchema } from "~~/lib/form";
import { formatErrorMessage } from "~~/lib/form/utils";
import { useAccountStore, useIdentityStore } from "~~/services/store";
import { notification } from "~~/utils/scaffold-eth";

Expand Down Expand Up @@ -120,7 +121,7 @@ export default function TransferContainer() {
setSelectedContactId(null);
} catch (error: any) {
console.error("Add to batch error:", error);
notification.error(error.message || "Failed to add to batch");
notification.error(formatErrorMessage(error, "Failed to add to batch"));
}
};

Expand Down
18 changes: 2 additions & 16 deletions packages/nextjs/components/contact-book/ContactPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ContactBookUserIcon } from "../icons/ContactBookUserIcon";
import { Contact } from "@polypay/shared";
import { Search, X } from "lucide-react";
import { useContacts, useGroups } from "~~/hooks";
import { useClickOutside } from "~~/hooks/useClickOutside";

interface ContactPickerProps {
accountId: string | null;
Expand Down Expand Up @@ -35,22 +36,7 @@ export function ContactPicker({ accountId, onSelect, disabled }: ContactPickerPr
}
}, [isOpen]);

// Close picker when clicking outside
useEffect(() => {
const handleClickOutside: (event: MouseEvent) => void = (event: MouseEvent) => {
if (pickerRef.current && !pickerRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};

if (isOpen) {
document.addEventListener("mousedown", handleClickOutside);
}

return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOpen]);
useClickOutside(pickerRef, () => setIsOpen(false), { isActive: isOpen });

// Filter contacts by search term
const filteredContacts = contacts.filter(
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/components/modals/ClaimRewardModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ModalContainer from "./ModalContainer";
import { useWalletClient } from "wagmi";
import { Button } from "~~/components/ui/button";
import { useClaimRewards, useClaimSummary } from "~~/hooks/api/useClaim";
import { formatErrorMessage } from "~~/lib/form/utils";
import { ModalProps } from "~~/types/modal";
import { chain } from "~~/utils/network-config";

Expand Down Expand Up @@ -63,7 +64,7 @@ const ClaimRewardModal: React.FC<ClaimRewardModalProps> = ({ isOpen, onClose, we
setTxHash(result.txHash);
setState("success");
} catch (error: any) {
setErrorMessage(error?.message || "Failed to claim rewards. Please try again.");
setErrorMessage(formatErrorMessage(error, "Failed to claim rewards. Please try again."));
setState("error");
}
};
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/components/modals/EditAccountModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import SubmittingStep from "./SubmittingStep";
import { ActionMode, ExistingSigner, ModalStep, PendingSigner } from "./types";
import ModalContainer from "~~/components/modals/ModalContainer";
import { useAccount, useMetaMultiSigWallet, useSignerTransaction } from "~~/hooks";
import { formatErrorMessage } from "~~/lib/form/utils";
import { useIdentityStore, useSidebarStore } from "~~/services/store";
import { ModalProps } from "~~/types/modal";
import { notification } from "~~/utils/scaffold-eth";
Expand Down Expand Up @@ -146,7 +147,7 @@ const EditAccountModal: React.FC<ModalProps> = ({ isOpen, onClose }) => {
closeManageAccounts();
} catch (error: any) {
console.error("Failed to submit proposal:", error);
notification.error(error.message || "Failed to submit proposal");
notification.error(formatErrorMessage(error, "Failed to submit proposal"));
setStep("confirm"); // Back to confirm on error
setIsSubmitting(false);
}
Expand Down
18 changes: 4 additions & 14 deletions packages/nextjs/components/popovers/ContactGroupPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"use client";

import React, { useEffect, useRef, useState } from "react";
import React, { useRef, useState } from "react";
import Image from "next/image";
import { Checkbox, SearchInput } from "../Common";
import { ContactGroup } from "@polypay/shared";
import { useContact, useGroups } from "~~/hooks/api/useContactBook";
import { useClickOutside } from "~~/hooks/useClickOutside";

interface ContactGroupPopoverProps {
contactId: string;
Expand All @@ -26,7 +27,7 @@ export function ContactGroupPopover({
selectedGroup,
selectedGroupIds,
onSelect,
arrowSrc = "/batch/popover-arrow.svg",
arrowSrc = "/icons/arrows/popover-arrow.svg",
arrowWidth = 28,
arrowHeight = 28,
popoverClassName,
Expand All @@ -38,18 +39,7 @@ export function ContactGroupPopover({
const [searchTerm, setSearchTerm] = useState("");
const rootRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!open) return;

const handleClickOutside = (event: MouseEvent) => {
if (rootRef.current && !rootRef.current.contains(event.target as Node)) {
setOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [open]);
useClickOutside(rootRef, () => setOpen(false), { isActive: open });

const contactGroupIds = selectedGroupIds || contact?.groups?.map(entry => entry.group?.id).filter(Boolean) || [];
const isLoading = isLoadingContact || isLoadingGroups;
Expand Down
19 changes: 2 additions & 17 deletions packages/nextjs/components/popovers/EditBatchPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ContactPicker } from "~~/components/contact-book/ContactPicker";
import { useContacts } from "~~/hooks";
import { useNetworkTokens } from "~~/hooks/app/useNetworkTokens";
import { useZodForm } from "~~/hooks/form";
import { useClickOutside } from "~~/hooks/useClickOutside";
import { editBatchSchema } from "~~/lib/form";
import { useAccountStore } from "~~/services/store";
import { formatAddress } from "~~/utils/format";
Expand Down Expand Up @@ -67,23 +68,7 @@ export default function EditBatchPopover({ item, isOpen, onClose, onSave, trigge
}
}, [isOpen, triggerRef]);

useEffect(() => {
if (!isOpen) return;

const handleClickOutside = (event: MouseEvent) => {
if (
popoverRef.current &&
!popoverRef.current.contains(event.target as Node) &&
triggerRef.current &&
!triggerRef.current.contains(event.target as Node)
) {
onClose();
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [isOpen, onClose, triggerRef]);
useClickOutside(popoverRef, onClose, { isActive: isOpen, triggerRef });

const handleContactSelect = (selectedAddress: string, name: string, contactId: string) => {
form.setValue("recipient", selectedAddress, { shouldValidate: true });
Expand Down
16 changes: 3 additions & 13 deletions packages/nextjs/components/popovers/TokenPillPopover.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"use client";

import React, { useEffect, useRef, useState } from "react";
import React, { useRef, useState } from "react";
import Image from "next/image";
import { ResolvedToken } from "@polypay/shared";
import { useMetaMultiSigWallet, useTokenPrices } from "~~/hooks";
import { useNetworkTokens } from "~~/hooks/app/useNetworkTokens";
import { useTokenBalances } from "~~/hooks/app/useTokenBalance";
import { useClickOutside } from "~~/hooks/useClickOutside";

interface TokenPillPopoverProps {
selectedToken: ResolvedToken;
Expand Down Expand Up @@ -34,18 +35,7 @@ export function TokenPillPopover({
const [open, setOpen] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (!open) return;

const handleClickOutside = (event: MouseEvent) => {
if (rootRef.current && !rootRef.current.contains(event.target as Node)) {
setOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [open]);
useClickOutside(rootRef, () => setOpen(false), { isActive: open });

const getTokenUsdValue = (token: ResolvedToken): string => {
const balance = parseFloat(balances[token.address] || "0");
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/hooks/app/transaction/useBatchTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useWalletClient } from "wagmi";
import { useMetaMultiSigWallet } from "~~/hooks";
import { useCreateTransaction, useReserveNonce } from "~~/hooks/api";
import { useGenerateProof } from "~~/hooks/app/useGenerateProof";
import { formatErrorMessage } from "~~/lib/form/utils";
import { useIdentityStore } from "~~/services/store";
import { notification } from "~~/utils/scaffold-eth";

Expand Down Expand Up @@ -100,7 +101,7 @@ export const useBatchTransaction = (options?: UseBatchTransactionOptions) => {
options?.onSuccess?.();
} catch (error: any) {
console.error("Propose batch error:", error);
notification.error(error.message || "Failed to propose batch");
notification.error(formatErrorMessage(error, "Failed to propose batch"));
} finally {
setIsLoading(false);
setLoadingState("");
Expand Down
7 changes: 4 additions & 3 deletions packages/nextjs/hooks/app/transaction/useSignerTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SignerData, TxType, encodeAddSigners, encodeRemoveSigners, encodeUpdate
import { useWalletClient } from "wagmi";
import { useGenerateProof, useMetaMultiSigWallet, useWalletCommitments, useWalletThreshold } from "~~/hooks";
import { useCreateTransaction, useReserveNonce } from "~~/hooks/api/useTransaction";
import { formatErrorMessage } from "~~/lib/form/utils";
import { notification } from "~~/utils/scaffold-eth";

interface UseSignerTransactionOptions {
Expand Down Expand Up @@ -107,7 +108,7 @@ export const useSignerTransaction = (options?: UseSignerTransactionOptions) => {
options?.onSuccess?.();
} catch (error: any) {
console.error("Failed to add signer:", error);
notification.error(error.message || "Failed to add signer");
notification.error(formatErrorMessage(error, "Failed to add signer"));
} finally {
setIsLoading(false);
setLoadingState("");
Expand Down Expand Up @@ -159,7 +160,7 @@ export const useSignerTransaction = (options?: UseSignerTransactionOptions) => {
options?.onSuccess?.();
} catch (error: any) {
console.error("Failed to remove signer:", error);
notification.error(error.message || "Failed to remove signer");
notification.error(formatErrorMessage(error, "Failed to remove signer"));
} finally {
setIsLoading(false);
setLoadingState("");
Expand Down Expand Up @@ -188,7 +189,7 @@ export const useSignerTransaction = (options?: UseSignerTransactionOptions) => {
options?.onSuccess?.();
} catch (error: any) {
console.error("Failed to update threshold:", error);
notification.error(error.message || "Failed to update threshold");
notification.error(formatErrorMessage(error, "Failed to update threshold"));
} finally {
setIsLoading(false);
setLoadingState("");
Expand Down
7 changes: 4 additions & 3 deletions packages/nextjs/hooks/app/transaction/useTransactionVote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useWalletClient } from "wagmi";
import { accountKeys, useMetaMultiSigWallet, userKeys } from "~~/hooks";
import { useApproveTransaction, useDenyTransaction, useExecuteTransaction } from "~~/hooks/api/useTransaction";
import { useGenerateProof } from "~~/hooks/app/useGenerateProof";
import { formatErrorMessage } from "~~/lib/form/utils";
import { useIdentityStore } from "~~/services/store/useIdentityStore";
import { notification } from "~~/utils/scaffold-eth";

Expand Down Expand Up @@ -175,7 +176,7 @@ export const useTransactionVote = (options?: UseTransactionVoteOptions) => {
options?.onSuccess?.();
} catch (error: any) {
console.error("Approve error:", error);
notification.error(error.message || "Failed to approve");
notification.error(formatErrorMessage(error, "Failed to approve"));
} finally {
setIsLoading(false);
setLoadingState("");
Expand Down Expand Up @@ -207,7 +208,7 @@ export const useTransactionVote = (options?: UseTransactionVoteOptions) => {
options?.onSuccess?.();
} catch (error: any) {
console.error("Deny error:", error);
notification.error(error.message || "Failed to deny");
notification.error(formatErrorMessage(error, "Failed to deny"));
} finally {
setIsLoading(false);
setLoadingState("");
Expand Down Expand Up @@ -245,7 +246,7 @@ export const useTransactionVote = (options?: UseTransactionVoteOptions) => {
options?.onSuccess?.();
} catch (error: any) {
console.error("Execute error:", error);
notification.error(error.message || "Failed to execute");
notification.error(formatErrorMessage(error, "Failed to execute"));
} finally {
setIsLoading(false);
setLoadingState("");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useWalletClient } from "wagmi";
import { useMetaMultiSigWallet } from "~~/hooks";
import { useCreateTransaction, useReserveNonce } from "~~/hooks/api/useTransaction";
import { useGenerateProof } from "~~/hooks/app/useGenerateProof";
import { formatErrorMessage } from "~~/lib/form/utils";
import { notification } from "~~/utils/scaffold-eth";

interface TransferParams {
Expand Down Expand Up @@ -101,7 +102,7 @@ export const useTransferTransaction = (options?: UseTransferTransactionOptions)
options?.onSuccess?.();
} catch (error: any) {
console.error("Transfer error:", error);
notification.error(error.message || "Failed to create transfer");
notification.error(formatErrorMessage(error, "Failed to create transfer"));
} finally {
setIsLoading(false);
setLoadingState("");
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/hooks/app/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useCallback, useState } from "react";
import { userKeys } from "../api";
import { useAuthProof } from "./useAuthProof";
import { useQueryClient } from "@tanstack/react-query";
import { formatErrorMessage } from "~~/lib/form/utils";
import { authApi } from "~~/services/api";
import { useIdentityStore } from "~~/services/store";

Expand Down Expand Up @@ -52,7 +53,7 @@ export const useAuth = () => {

return true;
} catch (err: any) {
setError(err.message || "Login failed");
setError(formatErrorMessage(err, "Login failed"));
return false;
} finally {
setIsLoggingIn(false);
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/hooks/app/useAuthProof.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState } from "react";
import { useWalletClient } from "wagmi";
import { formatErrorMessage } from "~~/lib/form/utils";
import { createCommitment, createSecret } from "~~/utils/multisig";

interface AuthProofResult {
Expand Down Expand Up @@ -69,7 +70,7 @@ export const useAuthProof = () => {
walletAddress, // For analytics only - NOT stored in database
};
} catch (err: any) {
setError(err.message || "Failed to generate auth proof");
setError(formatErrorMessage(err, "Failed to generate auth proof"));
return null;
} finally {
setIsGenerating(false);
Expand Down
Loading