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
30 changes: 29 additions & 1 deletion src/components/common/cardano-objects/connect-wallet.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Wallet, Loader2, CheckCircle2, AlertCircle } from "lucide-react";
import { Wallet, Loader2, CheckCircle2, AlertCircle, ShieldCheck } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import {
Expand Down Expand Up @@ -98,6 +98,7 @@ function ConnectWalletContent({
const { user, isLoading: isUserLoading } = useUser();
const userAddress = useUserStore((state) => state.userAddress);
const setUserAddress = useUserStore((state) => state.setUserAddress);
const requestReauth = useUserStore((state) => state.requestReauth);
const { toast } = useToast();

// Use WalletContext for regular wallet connection
Expand Down Expand Up @@ -654,6 +655,17 @@ function ConnectWalletContent({
</>
);
}
// Connected but the user query resolved with no user → no wallet session
// was established (authorization failed/cancelled). Don't spin forever:
// show an actionable "Authorize" label; the dropdown offers re-authorize.
if (isConnected && !user && !isUserLoading && !isConnecting) {
return (
<>
<ShieldCheck className="mr-2 h-4 w-4 transition-all duration-300" />
<span className="font-medium transition-opacity duration-300">Authorize</span>
</>
);
}
if (isConnected && isLoading) {
return (
<>
Expand Down Expand Up @@ -745,6 +757,22 @@ function ConnectWalletContent({

{isConnected && (
<>
{!user && !isUserLoading && (
<DropdownMenuItem
onClick={() => requestReauth()}
className={cn(
"px-3 py-2.5 rounded-md",
"text-zinc-900 dark:text-zinc-50",
"hover:bg-zinc-100 dark:hover:bg-zinc-800",
"focus:bg-zinc-100 dark:focus:bg-zinc-800",
"transition-colors duration-150",
"cursor-pointer"
)}
>
<ShieldCheck className="mr-2.5 h-4 w-4" />
<span className="font-medium">Authorize wallet</span>
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={handleDisconnect}
className={cn(
Expand Down
27 changes: 24 additions & 3 deletions src/components/common/overall-layout/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { publicRoutes } from "@/data/public-routes";
import { api } from "@/utils/api";
import useUser from "@/hooks/useUser";
import { useUserStore } from "@/lib/zustand/user";
import { normalizeAddressToBech32 } from "@/utils/addressCompatibility";
import useAppWallet from "@/hooks/useAppWallet";
import useUTXOS from "@/hooks/useUTXOS";
import useMeshWallet from "@/hooks/useMeshWallet";
Expand Down Expand Up @@ -102,7 +103,11 @@ export default function RootLayout({
// 1.9 IWallet bridge — used for getDRep(), which the react 2.0 wallet lacks.
const { wallet: meshWallet } = useMeshWallet();
const { state: walletState, connectedWalletInstance } = useWalletContext();
const address = useAddress();
// react-2.0's useAddress can return hex-encoded address bytes; user records
// and sessions are keyed by bech32. Normalize once at the source so every
// consumer below (store sync, session check) uses the bech32 form.
const rawAddress = useAddress();
const address = rawAddress ? normalizeAddressToBech32(rawAddress) : rawAddress;
const { user, isLoading: isLoadingUser } = useUser();
const router = useRouter();
const { appWallet } = useAppWallet();
Expand All @@ -111,6 +116,7 @@ export default function RootLayout({

const userAddress = useUserStore((state) => state.userAddress);
const setUserAddress = useUserStore((state) => state.setUserAddress);
const reauthNonce = useUserStore((state) => state.reauthNonce);
const ctx = api.useUtils();

// State for wallet authorization modal
Expand Down Expand Up @@ -257,15 +263,15 @@ export default function RootLayout({
activeWallet.getUsedAddresses()
.then((addresses) => {
if (addresses && addresses.length > 0) {
setUserAddress(addresses[0]!);
setUserAddress(normalizeAddressToBech32(addresses[0]!));
fetchingAddressRef.current = false;
} else {
return activeWallet.getUnusedAddresses();
}
})
.then((addresses) => {
if (addresses && addresses.length > 0 && !userAddress) {
setUserAddress(addresses[0]!);
setUserAddress(normalizeAddressToBech32(addresses[0]!));
}
fetchingAddressRef.current = false;
})
Expand Down Expand Up @@ -398,6 +404,21 @@ export default function RootLayout({
// Don't refetch here - let the natural query refetch handle it if needed
}, []);

// Manual re-authorization: when the user is connected but never got a
// session (e.g. the auto-authorize failed/was cancelled), hasCheckedSession
// stays true and the modal never reopens — leaving the connect button stuck
// on "Loading…". Bumping reauthNonce (from the connect dropdown) clears that
// latch and refetches the session so the session-check effect reopens the
// auth modal.
useEffect(() => {
if (reauthNonce > 0) {
setHasCheckedSession(false);
setCheckingSession(false);
setShowAuthModal(false);
void refetchWalletSession();
}
}, [reauthNonce, refetchWalletSession]);

const handleAuthModalAuthorized = useCallback(async () => {
setShowAuthModal(false);
setCheckingSession(false);
Expand Down
7 changes: 7 additions & 0 deletions src/lib/zustand/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ interface UserState {
setPastWallet: (pastWallet: string | undefined) => void;
pastUtxosEnabled: boolean;
setPastUtxosEnabled: (enabled: boolean | ((prev: boolean) => boolean)) => void;
// Bumped when the user explicitly asks to re-run wallet authorization
// (e.g. after a failed/cancelled auto-authorize left them connected but
// unauthorized). The layout watches this to reopen the auth modal.
reauthNonce: number;
requestReauth: () => void;
}

export const useUserStore = create<UserState>()(
Expand All @@ -36,6 +41,8 @@ export const useUserStore = create<UserState>()(
setUser: (user) => set({ user }),
pastWallet: undefined,
setPastWallet: (wallet) => set({ pastWallet: wallet }),
reauthNonce: 0,
requestReauth: () => set((state) => ({ reauthNonce: state.reauthNonce + 1 })),
pastUtxosEnabled: false,
setPastUtxosEnabled: (enabled) => {
const newValue = typeof enabled === "function" ? enabled(get().pastUtxosEnabled) : enabled;
Expand Down
Loading