feat: add wallet provider foundation
This commit is contained in:
136
src/wallet/WalletProvider.tsx
Normal file
136
src/wallet/WalletProvider.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useToast } from "../components/Toast";
|
||||
import { useI18n } from "../i18n";
|
||||
import { fetchWalletMe } from "./api";
|
||||
import { signInWithInjectedWallet } from "./injected";
|
||||
import { clearWalletToken, readWalletToken, writeWalletToken } from "./token";
|
||||
|
||||
type WalletStatus = "loading" | "loggedOut" | "loggedIn";
|
||||
|
||||
type WalletContextValue = {
|
||||
address: string | null;
|
||||
token: string | null;
|
||||
status: WalletStatus;
|
||||
loginModalOpen: boolean;
|
||||
openLoginModal: () => void;
|
||||
closeLoginModal: () => void;
|
||||
signInInjected: () => Promise<void>;
|
||||
completeLogin: (token: string, wallet: string) => void;
|
||||
logout: () => void;
|
||||
};
|
||||
|
||||
const WalletContext = createContext<WalletContextValue | null>(null);
|
||||
|
||||
export function shortenAddress(address: string): string {
|
||||
if (address.length <= 12) return address;
|
||||
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
||||
}
|
||||
|
||||
export function WalletProvider({ children }: { children: ReactNode }) {
|
||||
const { t } = useI18n();
|
||||
const { showToast } = useToast();
|
||||
const [token, setToken] = useState<string | null>(() => readWalletToken());
|
||||
const [address, setAddress] = useState<string | null>(null);
|
||||
const [status, setStatus] = useState<WalletStatus>(
|
||||
token ? "loading" : "loggedOut",
|
||||
);
|
||||
const [loginModalOpen, setLoginModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
if (!token) {
|
||||
setStatus("loggedOut");
|
||||
setAddress(null);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("loading");
|
||||
fetchWalletMe(token)
|
||||
.then((me) => {
|
||||
if (cancelled) return;
|
||||
setAddress(me.wallet);
|
||||
setStatus("loggedIn");
|
||||
})
|
||||
.catch(() => {
|
||||
if (cancelled) return;
|
||||
clearWalletToken();
|
||||
setToken(null);
|
||||
setAddress(null);
|
||||
setStatus("loggedOut");
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [token]);
|
||||
|
||||
const completeLogin = useCallback((nextToken: string, wallet: string) => {
|
||||
writeWalletToken(nextToken);
|
||||
setToken(nextToken);
|
||||
setAddress(wallet);
|
||||
setStatus("loggedIn");
|
||||
setLoginModalOpen(false);
|
||||
}, []);
|
||||
|
||||
const signInInjected = useCallback(async () => {
|
||||
try {
|
||||
const res = await signInWithInjectedWallet();
|
||||
completeLogin(res.token, res.wallet);
|
||||
showToast(t("walletLoginSuccess"));
|
||||
} catch (error) {
|
||||
const message =
|
||||
error instanceof Error ? error.message : t("walletLoginFailed");
|
||||
showToast(message || t("walletLoginFailed"), "error");
|
||||
throw error;
|
||||
}
|
||||
}, [completeLogin, showToast, t]);
|
||||
|
||||
const logout = useCallback(() => {
|
||||
clearWalletToken();
|
||||
setToken(null);
|
||||
setAddress(null);
|
||||
setStatus("loggedOut");
|
||||
showToast(t("walletDisconnected"));
|
||||
}, [showToast, t]);
|
||||
|
||||
const value = useMemo<WalletContextValue>(
|
||||
() => ({
|
||||
address,
|
||||
token,
|
||||
status,
|
||||
loginModalOpen,
|
||||
openLoginModal: () => setLoginModalOpen(true),
|
||||
closeLoginModal: () => setLoginModalOpen(false),
|
||||
signInInjected,
|
||||
completeLogin,
|
||||
logout,
|
||||
}),
|
||||
[
|
||||
address,
|
||||
completeLogin,
|
||||
loginModalOpen,
|
||||
logout,
|
||||
signInInjected,
|
||||
status,
|
||||
token,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<WalletContext.Provider value={value}>{children}</WalletContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useWallet() {
|
||||
const ctx = useContext(WalletContext);
|
||||
if (!ctx) throw new Error("useWallet must be used within WalletProvider");
|
||||
return ctx;
|
||||
}
|
||||
Reference in New Issue
Block a user