Skip to content

Commit

Permalink
feat: enable account switch/disconnect wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
Cifko committed Jan 24, 2025
1 parent 98aee2e commit 20ba7ca
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 81 deletions.
5 changes: 3 additions & 2 deletions app/GlobalStateContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
import ModalError from "./ModalError";
import ZkLogin from "@/lib/zklogin";
import { LOCAL_STORAGE_ACCESS_TOKEN, LOCAL_STORAGE_ID_TOKEN } from "@/lib/local_storage_consts";


export type LoginState = 'loggedIn' | 'loggedOut' | 'loggingIn';
Expand All @@ -21,8 +22,8 @@ export const GlobalStateProvider = ({ children }: { children: ReactNode }) => {
const [zkLogin] = useState(new ZkLogin(setLogState));

useEffect(() => {
if (!localStorage.getItem("id_token")) {
if (localStorage.getItem("access_token")) {
if (!localStorage.getItem(LOCAL_STORAGE_ID_TOKEN)) {
if (localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN)) {
// This is when the user is logged in but not via zklogin. Zklogin will check the state of the user and set the state accordingly.
setLogState('loggedIn');
} else {
Expand Down
14 changes: 3 additions & 11 deletions components/cloud-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ import {
useSignPersonalMessage,
useSuiClient,
} from "@mysten/dapp-kit";
import { toBase64 } from "@mysten/sui/utils";
import { GetApiSample } from "@/components/ui/GetApiSample";
import Image from "next/image";
import { formatNumber, simplifyModelName } from "@/lib/utils";
import { LOCAL_STORAGE_ACCESS_TOKEN } from "@/lib/local_storage_consts";

type TabType = "compute" | "models" | "api" | "billing" | "docs" | "calculator";

Expand Down Expand Up @@ -126,8 +126,6 @@ export function CloudView() {
const [partialBalance, setPartialBalance] = useState<number | undefined>();
const { logState, setLogState } = useGlobalState();

console.log("balance", balance);
console.log("partialBalance", partialBalance);
useEffect(() => {
getTasks()
.then((tasks_with_modalities) => {
Expand All @@ -143,7 +141,6 @@ export function CloudView() {
if (logState === "loggedIn") {
getAllStacks()
.then((stacks) => {
console.log("stacks", stacks);
let partialBalance = 0;
stacks.forEach(([stack]) => {
partialBalance +=
Expand Down Expand Up @@ -484,7 +481,6 @@ export function CloudView() {
return;
}
getSuiAddress().then((suiAddress) => {
// console.log('suiAddress', suiAddress)
setWalletConfirmed(suiAddress != null && suiAddress == account?.address);
});
}, [account]);
Expand All @@ -493,7 +489,7 @@ export function CloudView() {
if (account == null) {
return;
}
const access_token = localStorage.getItem("access_token");
const access_token = localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN);
let user_id;
if (access_token) {
const base64Url = access_token.split(".")[1];
Expand Down Expand Up @@ -530,17 +526,13 @@ export function CloudView() {

const handleUSDCPayment = async (amount: number) => {
setHandlingPayment(true);
console.log("zklogin", zkLogin.isEnabled);
if (zkLogin.isEnabled) {
zkLogin
.payUSDC(amount * 1000000, suiClient)
.then((res) => {
console.log(res);
const txDigest = res.digest;
// const txDigest = "ASp9K5Ms1HS1sKW2H4oa4Q9q6Zz3kBqKUn3x9JbZcGsw";
console.log("txDigest", txDigest);
zkLogin.signMessage(txDigest).then((proofSignature) => {
console.log("proofSignature", proofSignature);
setTimeout(() => {
usdcPayment(txDigest, proofSignature)
.then(() => {
Expand Down Expand Up @@ -665,7 +657,7 @@ export function CloudView() {
className="w-full justify-start bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-600"
disabled={handlingPayment}
>
{walletConfirmed ? "Pay with USDC" : "Confirm Wallet"}
{walletConfirmed ? "Pay with USDC" : "Confirm Account"}
</Button>
) : (
<ConnectModal
Expand Down
6 changes: 3 additions & 3 deletions components/compute-units-payment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ConnectModal, useCurrentAccount, useCurrentWallet, useSignAndExecuteTra
import { getSuiAddress, ModelModality, payUSDC, proofRequest, usdcPayment } from "@/lib/atoma"
import { useGlobalState } from "@/app/GlobalStateContext"
import { GetApiSample } from "@/components/ui/GetApiSample"
import { LOCAL_STORAGE_ACCESS_TOKEN } from "@/lib/local_storage_consts"

interface ComputeUnitsPaymentProps {
modelName: string
Expand Down Expand Up @@ -45,7 +46,7 @@ export function ComputeUnitsPayment({ modelName, features, pricePer1MUnits, onCl
if (account == null) {
return;
}
const access_token = localStorage.getItem("access_token");
const access_token = localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN);
let user_id;
if (access_token) {
const base64Url = access_token.split('.')[1];
Expand Down Expand Up @@ -86,7 +87,6 @@ export function ComputeUnitsPayment({ modelName, features, pricePer1MUnits, onCl
const txDigest = (res as { digest: string }).digest;
setTimeout(() => {
usdcPayment(txDigest).then((res) => {
console.log('res', res)
handleNextStep();
}).catch((error:Response) => {
setError(`${error.status} : ${error.statusText}`);
Expand Down Expand Up @@ -148,7 +148,7 @@ export function ComputeUnitsPayment({ modelName, features, pricePer1MUnits, onCl
<div className="space-y-4">
{connectionStatus == "connected" ? (
<Button onClick={() => walletConfirmed?handleUSDCPayment():handleConfirmWallet()} className="w-full justify-start bg-white dark:bg-gray-700 text-gray-800 dark:text-gray-200 border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-600">
{walletConfirmed?"Pay with USDC":"Confirm Wallet"}
{walletConfirmed?"Pay with USDC":"Confirm Account"}
</Button>
) : (
<ConnectModal
Expand Down
5 changes: 3 additions & 2 deletions components/login-register-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Label } from "@/components/ui/label";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog";
import { loginUser, registerUser } from "@/lib/atoma";
import { useGlobalState } from "@/app/GlobalStateContext";
import { LOCAL_STORAGE_ACCESS_TOKEN } from "@/lib/local_storage_consts";

interface LoginRegisterModalProps {
isOpen: boolean;
Expand All @@ -31,7 +32,7 @@ export function LoginRegisterModal({ isOpen, onClose }: LoginRegisterModalProps)
const onLogin = () => {
loginUser(email, password)
.then(({ access_token, refresh_token }) => {
localStorage.setItem("access_token", access_token);
localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN, access_token);
document.cookie = `refresh_token=${refresh_token}; path=/; secure; HttpOnly; SameSite=Strict`;
setLogState('loggedIn');
onClose();
Expand All @@ -48,7 +49,7 @@ export function LoginRegisterModal({ isOpen, onClose }: LoginRegisterModalProps)
const onRegister = () => {
registerUser(email, password)
.then(({ access_token, refresh_token }) => {
localStorage.setItem("access_token", access_token);
localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN, access_token);
document.cookie = `refresh_token=${refresh_token}; path=/; secure; HttpOnly; SameSite=Strict`;
setLogState('loggedIn');
onClose();
Expand Down
3 changes: 0 additions & 3 deletions components/node-status-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ export function NodeStatusView() {
// const [statsStack, setStatsStacks] = useState<unknown[]>([]);
// const [computeUnits, setComputeUnits] = useState<ComputedUnitsProcessedResponse[]>([]);
// const [latency, setLatency] = useState<LatencyResponse[]>([]);
// console.log('networkActivityData',networkActivityData)
// console.log('computeUnitsData', computeUnitsData)
// console.log('activityModels', activityModels)

useEffect(() => {
getNodesDistribution().then((nodes) => {
Expand Down
171 changes: 141 additions & 30 deletions components/user-profile-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,172 @@
import { User } from 'lucide-react'
import { Button } from "@/components/ui/button"
import { User } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { useGlobalState } from '@/app/GlobalStateContext';
import { useEffect, useState } from 'react';
import { getUserProfile } from '@/lib/atoma';
import { LOCAL_STORAGE_MAX_EPOCH, LOCAL_STORAGE_RANDOMNESS, LOCAL_STORAGE_SECRET_KEY, LOCAL_STORAGE_ZKP } from '@/lib/zklogin';
} from "@/components/ui/dropdown-menu";
import { useGlobalState } from "@/app/GlobalStateContext";
import { useEffect, useState } from "react";
import { getUserProfile } from "@/lib/atoma";
import {
ConnectModal,
useCurrentAccount,
useCurrentWallet,
useDisconnectWallet,
useSwitchAccount,
} from "@mysten/dapp-kit";
import { LOCAL_STORAGE_ACCESS_TOKEN } from "@/lib/local_storage_consts";
import Image from "next/image";

export function UserProfileIcon() {
const { setLogState } = useGlobalState();
const [username, setUsername] = useState<string | null>(null);
const account = useCurrentAccount();
const [address, setAddress] = useState<string | null>(null);
const [isCopied, setIsCopied] = useState(false);
const { isConnected } = useCurrentWallet();
const { currentWallet } = useCurrentWallet();
const { mutateAsync: switchAccount } = useSwitchAccount();
const { mutateAsync: walletDisconnect } = useDisconnectWallet();
const { zkLogin } = useGlobalState();

useEffect(() => {
getUserProfile().then((profile) => {
setUsername(profile.username);
});
},[])
}, []);

const handleLogOut = () => {
setLogState('loggedOut');
window.localStorage.removeItem("access_token");
window.localStorage.removeItem("id_token");
localStorage.removeItem(LOCAL_STORAGE_SECRET_KEY);
localStorage.removeItem(LOCAL_STORAGE_RANDOMNESS);
localStorage.removeItem(LOCAL_STORAGE_MAX_EPOCH);
localStorage.removeItem(LOCAL_STORAGE_ZKP);
}
setLogState("loggedOut");
window.localStorage.removeItem(LOCAL_STORAGE_ACCESS_TOKEN);
zkLogin?.disconnect();
setAddress(null);
};

const handleWalletDisconnect = () => {
if (zkLogin?.zkLoginUserAddressValue) {
zkLogin.disconnect();
if (isConnected) {
walletDisconnect().catch((error) => {
console.error("Error disconnecting wallet", error);
});
}
} else {
walletDisconnect().catch((error) => {
console.error("Error disconnecting wallet", error);
});
}
setAddress(null);
};

const truncateAddress = (address: string) => {
if (!address) return "";
return `${address.slice(0, 6)}..${address.slice(-4)}`;
};

useEffect(() => {
if (zkLogin?.zkLoginUserAddressValue) {
setAddress(zkLogin?.zkLoginUserAddressValue);
} else if (account?.address) {
setAddress(account?.address);
} else {
setAddress(null);
}
}, [account, zkLogin?.zkLoginUserAddressValue]);

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="relative h-8 w-8 rounded-full hover:bg-purple-100 dark:hover:bg-purple-900"
<Button
variant="ghost"
className="relative h-8 w-8 rounded-full hover:bg-purple-100 dark:hover:bg-purple-900 transition-colors"
>
<User className="h-5 w-5 text-purple-600 dark:text-purple-300" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-64 bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-800 text-gray-900 dark:text-white"
align="end"
<DropdownMenuContent
className="w-72 bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-800 shadow-lg animate-in fade-in-0 zoom-in-95"
align="end"
sideOffset={8}
forceMount
>
<div className="px-4 py-3 border-b border-gray-200 dark:border-gray-800">
<p className="text-sm text-gray-600 dark:text-gray-400">{username}</p>
{/* Email Section */}
<div className="px-4 py-3">
<p className="text-sm font-medium text-gray-900 dark:text-white">{username}</p>
</div>
<div className="py-2">
<DropdownMenuItem className="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer" onClick={handleLogOut}>
Log out

<DropdownMenuSeparator className="bg-gray-200 dark:bg-gray-800 my-2" />

{/* Wallet Section */}
{address && (
<div className="px-4 py-3">
<div className="flex items-center justify-between">
<p className="text-sm font-medium text-gray-900 dark:text-white">SuiAccount</p>
<DropdownMenu>
<div className="flex flex-row gap-2">
<DropdownMenuTrigger asChild>
<p className="text-sm font-medium text-gray-900 dark:text-white font-mono cursor-pointer">
{truncateAddress(address)}
</p>
</DropdownMenuTrigger>
<Image
src={isCopied ? "/copy-done.svg" : "/copy.svg"}
className="cursor-pointer dark:invert"
alt="copy"
title="Copy to clipboard"
width={14}
height={14}
onClick={() => {
navigator.clipboard.writeText(address);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 1000);
}}
/>
</div>
<DropdownMenuContent>
{currentWallet?.accounts.map((acc) => (
<DropdownMenuItem
key={acc.address}
onClick={() => switchAccount({ account: acc })}
className="w-full px-4 py-3 text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors rounded-md"
title={acc.address}
>
{acc.label || truncateAddress(acc.address)}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
)}

{/* Actions Section */}
{address ? (
<DropdownMenuItem
className="w-full px-4 py-3 text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors rounded-md"
onClick={handleWalletDisconnect}
>
Disconnect Wallet
</DropdownMenuItem>
</div>
) : (
<ConnectModal
trigger={
<span className="relative flex select-none items-center gap-2 outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 w-full px-4 py-3 text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors rounded-md">
Connect sui wallet
</span>
}
/>
)}
<DropdownMenuSeparator className="bg-gray-200 dark:bg-gray-800 my-2" />
<DropdownMenuItem
className="w-full px-4 py-3 text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer transition-colors rounded-md"
onClick={handleLogOut}
>
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
);
}

6 changes: 3 additions & 3 deletions lib/atoma.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { SuiClient } from "@mysten/sui/client";
import { Transaction } from "@mysten/sui/transactions";
import type { UseMutateAsyncFunction } from "@tanstack/react-query";
import { WalletAccount } from "@mysten/wallet-standard";
import { LOCAL_STORAGE_ACCESS_TOKEN } from "./local_storage_consts";

const proxy_url = process.env.NEXT_PUBLIC_PROXY_URL;
const USDC_TYPE = process.env.NEXT_PUBLIC_USDC_TYPE;
Expand Down Expand Up @@ -116,15 +117,15 @@ const request = async <T>({ path, post, body, use_auth }: RequestOptions): Promi
}
if (use_auth) {
options.headers = {
Authorization: `Bearer ${localStorage.getItem("access_token")}`,
Authorization: `Bearer ${localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN)}`,
...options.headers,
};
}

return await fetch(`${proxy_url}/${path}`, options).then((response) => {
if (!response.ok) {
if (response.status === 401) {
localStorage.removeItem("access_token");
localStorage.removeItem(LOCAL_STORAGE_ACCESS_TOKEN);
}
throw response;
}
Expand Down Expand Up @@ -238,7 +239,6 @@ export const payUSDC = async (

for (const coin of coins) {
if (parseInt(coin.balance) >= remainingAmount) {
console.log("add coin", coin.coinObjectId, remainingAmount);
const [splitCoin] = tx.splitCoins(coin.coinObjectId, [tx.pure.u64(remainingAmount)]);
selectedCoins.push(splitCoin);
remainingAmount = 0;
Expand Down
Loading

0 comments on commit 20ba7ca

Please sign in to comment.