Skip to content

Commit e8aeda4

Browse files
committed
feat: added avatar lookup
1 parent e11474d commit e8aeda4

File tree

4 files changed

+110
-7
lines changed

4 files changed

+110
-7
lines changed

components/AvatarViewer.vue

+4-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
<div class="p-4 bg-neutral-100 text-neutral-400/75 rounded-2xl truncate">{{ avatarInfo.avatar?.tokenAddress }}</div>
1010
<label class="mt-2 px-2 text-sm text-neutral-400/75">Token ID</label>
1111
<div class="p-4 bg-neutral-100 text-neutral-400/75 rounded-2xl truncate">{{ avatarInfo.avatar?.tokenId }}</div>
12-
<div class="p-4 rounded-2xl truncate" :class="{ 'bg-green-100 text-green-500': avatarInfo.owned, 'bg-red-100 text-red-500': !avatarInfo.owned }">{{ avatarInfo.owned ? "Owned" : "Not owned" }}</div>
12+
<template v-if="avatarInfo.avatar?.tokenAddress !== zeroAddress">
13+
<div class="p-4 rounded-2xl truncate" :class="{ 'bg-green-100 text-green-500': avatarInfo.owned, 'bg-red-100 text-red-500': !avatarInfo.owned }">{{ avatarInfo.owned ? "Owned" : "Not owned" }}</div>
14+
</template>
1315
<template v-if="avatarMetadata?.collection?.name">
1416
<template v-if="avatarMetadata?.collection?.verified">
1517
<VerifiedNotice />
@@ -40,10 +42,7 @@
4042
</div>
4143
</div>
4244
</template>
43-
<template v-else-if="avatarInfo.avatar?.tokenAddress === zeroAddress">
44-
<VerifiedNotice />
45-
</template>
46-
<template v-else>
45+
<template v-else-if="avatarInfo.avatar?.tokenAddress !== zeroAddress">
4746
<UnknownNotice />
4847
</template>
4948
</div>

components/Navbar.vue

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,19 @@
44
<div class="hidden lg:flex items-center gap-8">
55
<div class="flex items-center gap-2">
66
<Logo class="w-7 h-7" />
7-
<h1 class="font-semibold text-xl text-sky-500">ethereum avatar service</h1>
7+
<h1 class="pb-1 font-semibold text-xl text-sky-500">ethereum avatar service</h1>
88
</div>
99
<div class="flex items-center gap-2">
1010
<NuxtLink replace to="/" class="link" active-class="link-active">My avatar</NuxtLink>
11+
<NuxtLink replace to="/lookup" class="link" active-class="link-active" :class="{ 'link-active': route.path.includes('/lookup') }">Lookup</NuxtLink>
1112
<NuxtLink replace to="/whitelist" class="link" active-class="link-active">Verified collections</NuxtLink>
1213
<NuxtLink replace to="/docs" class="link" active-class="link-active">Docs</NuxtLink>
1314
</div>
1415
</div>
1516
<div class="flex lg:hidden items-center gap-2">
1617
<div class="flex items-center gap-2">
1718
<Logo class="w-7 h-7" />
18-
<span class="font-semibold text-3xl text-sky-500">eas</span>
19+
<span class="pb-1 font-semibold text-3xl text-sky-500">eas</span>
1920
</div>
2021
<NavbarHamburger />
2122
</div>
@@ -46,6 +47,7 @@ import Logo from "~/components/icons/Logo.vue";
4647
const { connect } = useConnect();
4748
const { disconnect } = useDisconnect();
4849
const { address, isConnected } = useAccount();
50+
const route = useRoute();
4951
</script>
5052

5153
<style scoped>

components/NavbarHamburger.vue

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<template v-if="isSelecting">
88
<div class="absolute mt-2 p-2 left-4 right-4 flex flex-col bg-neutral-50 border border-neutral-100 shadow-lg shadow-neutral-100 rounded-2xl duration-300">
99
<NuxtLink replace to="/" class="link" active-class="link-active">My avatar</NuxtLink>
10+
<NuxtLink replace to="/lookup" class="link" active-class="link-active" :class="{ 'link-active': route.path.includes('/lookup') }">Lookup</NuxtLink>
1011
<NuxtLink replace to="/whitelist" class="link" active-class="link-active">Verified collections</NuxtLink>
1112
<NuxtLink replace to="/docs" class="link" active-class="link-active">Docs</NuxtLink>
1213
</div>
@@ -18,6 +19,8 @@
1819
<script setup lang="ts">
1920
import {Bars3Icon} from "@heroicons/vue/20/solid";
2021
22+
const route = useRoute();
23+
2124
const isSelecting = ref(false);
2225
</script>
2326

pages/lookup/[[walletAddress]].vue

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<template>
2+
<div class="py-8 lg:py-16">
3+
<div class="max-w-lg mx-auto flex flex-col gap-12">
4+
<h1 class="font-medium text-center text-2xl">Lookup avatar on {{ chain.name }}</h1>
5+
<div class="flex gap-2">
6+
<input class="input" type="text" placeholder="Search on address.." v-model="walletAddress">
7+
<button :disabled="!isSearchValid" @click="search" class="px-6 h-14 flex gap-2 flex-nowrap justify-center items-center bg-blue-500/20 hover:bg-blue-500/25 disabled:bg-neutral-100 font-medium text-blue-500 disabled:text-neutral-400/75 rounded-2xl duration-300">Go</button>
8+
</div>
9+
<template v-if="fetchedWalletAddress">
10+
<AvatarViewer :avatar-info="avatarInfo" :avatar-metadata="avatarMetadata" :image-link="imageLink" />
11+
</template>
12+
</div>
13+
</div>
14+
</template>
15+
16+
<script setup>
17+
import {onMounted, ref} from "vue";
18+
import {useAccount, useConnect} from "@wagmi/vue";
19+
import {isAddress, zeroAddress} from "viem";
20+
import AvatarViewer from "~/components/AvatarViewer.vue";
21+
22+
const { connect } = useConnect();
23+
const { chainId, chain } = useAccount();
24+
const {setAvatar, getAvatarInfo} = useAvatarService();
25+
const {getAvatarForAddress} = useAvatarServiceApi();
26+
const route = useRoute();
27+
const router = useRouter();
28+
29+
const IPFS_GATEWAY = "https://ipfs.io/ipfs/";
30+
31+
const walletAddress = ref(route.params.walletAddress ?? "");
32+
const fetchedWalletAddress = ref("");
33+
const avatarInfo = ref(null);
34+
const avatarMetadata = ref(null);
35+
const imageLink = ref("");
36+
37+
onMounted(() => {
38+
if (walletAddress.value) {
39+
updateAvatarInfo();
40+
}
41+
});
42+
43+
watch(chainId, () => {
44+
updateAvatarInfo();
45+
});
46+
47+
const isSearchValid = computed(() => {
48+
return walletAddress.value && isAddress(walletAddress.value) && walletAddress.value !== fetchedWalletAddress.value;
49+
});
50+
51+
function search() {
52+
router.push({
53+
params: { walletAddress: walletAddress.value }
54+
});
55+
56+
updateAvatarInfo();
57+
}
58+
59+
async function updateAvatarInfo() {
60+
if (!isSearchValid) return;
61+
62+
try {
63+
const address = walletAddress.value;
64+
65+
const apiRes = await getAvatarForAddress(address);
66+
67+
const chainName = chain.value.name.toLowerCase();
68+
69+
avatarInfo.value = {
70+
avatar: {
71+
tokenAddress: apiRes["networks"][chainName]["flat"]["avatar"]["token_address"],
72+
tokenId: apiRes["networks"][chainName]["flat"]["avatar"]["token_id"]
73+
},
74+
owned: apiRes["networks"][chainName]["flat"]["owned"],
75+
uri: apiRes["networks"][chainName]["flat"]["uri"]
76+
}
77+
78+
avatarMetadata.value = apiRes["networks"][chainName]["flat"]["avatar_metadata"];
79+
80+
let src = avatarMetadata.value["image"];
81+
82+
if (src && src.startsWith("ipfs://")) {
83+
src = src.replace("ipfs://", IPFS_GATEWAY);
84+
}
85+
86+
imageLink.value = src;
87+
88+
fetchedWalletAddress.value = address;
89+
} catch(error) {
90+
console.error("Error getting avatar:", error);
91+
}
92+
}
93+
</script>
94+
95+
<style scoped>
96+
.input {
97+
@apply bg-neutral-100 p-4 placeholder-neutral-300 rounded-2xl w-full;
98+
}
99+
</style>

0 commit comments

Comments
 (0)