diff --git a/src/app/requests/RequestsPageClient.tsx b/src/app/requests/RequestsPageClient.tsx index 4e03d0c..3981f97 100644 --- a/src/app/requests/RequestsPageClient.tsx +++ b/src/app/requests/RequestsPageClient.tsx @@ -1,183 +1,234 @@ "use client"; -import { useState } from "react"; -import { Nav } from "@/components/Nav"; -import { ConnectWalletButton } from "@/components/ConnectWalletButton"; -import { useWalletStore } from "@/store/wallet"; -import { useTranslation } from "@/lib/i18n/I18nProvider"; -import type { MessageKey } from "@/lib/i18n"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useI18n } from "@/lib/i18n/I18nProvider"; +import { useWallet } from "@/lib/wallet/WalletProvider"; +import { useDebouncedValue } from "@/hooks/useDebouncedValue"; import { getSupportRequests, - JUSTIFICATION_MAX_LENGTH, - NAME_MAX_LENGTH, submitSupportRequest, upvoteSupportRequest, - type SubmitError, + isAlreadySupported, + NAME_MIN_LENGTH, + NAME_MAX_LENGTH, + JUSTIFICATION_MIN_LENGTH, + JUSTIFICATION_MAX_LENGTH, + type SupportRequest, type SupportRequestKind, + type SubmitError, } from "@/lib/supportRequestStore"; -const SUBMIT_ERROR_KEY: Record = { - "name-length": "requests.error.nameLength", - "justification-length": "requests.error.justificationLength", - "already-supported": "requests.error.alreadySupported", - duplicate: "requests.error.duplicate", -}; +type SubmitState = + | { status: "idle" } + | { status: "submitting" } + | { status: "error"; error: SubmitError; existingId?: string } + | { status: "success" }; export default function RequestsPageClient() { - const { t } = useTranslation(); - const { isConnected, address } = useWalletStore(); - const [requests, setRequests] = useState(getSupportRequests); + const { t } = useI18n(); + const { address } = useWallet(); + + const [requests, setRequests] = useState([]); const [kind, setKind] = useState("chain"); const [name, setName] = useState(""); const [justification, setJustification] = useState(""); - const [error, setError] = useState(null); - - const refresh = () => setRequests(getSupportRequests()); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - if (!address) return; - const result = submitSupportRequest(address, { kind, name, justification }); - if (!result.ok) { - setError(t(SUBMIT_ERROR_KEY[result.error], { name: name.trim() })); - return; - } - setError(null); - setName(""); - setJustification(""); - refresh(); - }; + const [submitState, setSubmitState] = useState({ status: "idle" }); + const [upvoteError, setUpvoteError] = useState(null); + + const debouncedName = useDebouncedValue(name, 300); - const handleUpvote = (id: string) => { - if (!address) return; - upvoteSupportRequest(id, address); + const refresh = useCallback(() => { + setRequests(getSupportRequests()); + }, []); + + useEffect(() => { refresh(); - }; + }, [refresh]); + + const alreadySupported = useMemo( + () => (name.trim() ? isAlreadySupported(kind, name) : false), + [kind, name], + ); + + const liveAlreadySupported = useMemo( + () => (debouncedName.trim() ? isAlreadySupported(kind, debouncedName) : false), + [kind, debouncedName], + ); + + const handleSubmit = useCallback( + (event: React.FormEvent) => { + event.preventDefault(); + if (!address) return; + const result = submitSupportRequest(address, { kind, name, justification }); + if (result.ok) { + setSubmitState({ status: "success" }); + setName(""); + setJustification(""); + refresh(); + return; + } + setSubmitState({ status: "error", error: result.error, existingId: result.existingId }); + }, + [address, kind, name, justification, refresh], + ); + + const handleUpvote = useCallback( + (id: string) => { + if (!address) return; + setUpvoteError(null); + const result = upvoteSupportRequest(id, address); + if (!result.ok) { + setUpvoteError(t(`requests.upvoteError.${result.error}`)); + return; + } + refresh(); + }, + [address, refresh, t], + ); + + const handleDuplicateUpvote = useCallback(() => { + if (submitState.status !== "error" || !submitState.existingId) return; + handleUpvote(submitState.existingId); + setSubmitState({ status: "idle" }); + }, [submitState, handleUpvote]); return ( -
-