Skip to content

Commit 696fbfc

Browse files
committed
add Avatar component
1 parent 04e8cf2 commit 696fbfc

File tree

3 files changed

+113
-33
lines changed

3 files changed

+113
-33
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { Box, Image, Spinner } from "@chakra-ui/react";
2+
3+
import { useConnection } from "hooks/connection";
4+
import { PropsWithChildren, useEffect, useState } from "react";
5+
import { cairo, shortString } from "starknet";
6+
import { selectors, VERSION } from "utils/selectors";
7+
import Storage from "utils/storage";
8+
9+
const avatarAddress =
10+
"0x56be7d500bd759ac4f96f786f15e9e4702c1ae0582091b20c90546e44ba42fc";
11+
12+
export const stringFromByteArray = (arr: string[]) => {
13+
arr = arr.slice(1, -1);
14+
while (arr.length > 0 && arr[arr.length - 1] === "0x0") {
15+
arr = arr.slice(0, -1);
16+
}
17+
18+
return arr.map((i) => shortString.decodeShortString(i)).join("");
19+
};
20+
21+
export function Avatar({ children }: PropsWithChildren) {
22+
const { controller } = useConnection();
23+
24+
const [image, setImage] = useState("");
25+
const [isLoading, setIsLoading] = useState(false);
26+
27+
useEffect(() => {
28+
const avatarSelector = selectors[VERSION].avatar(controller.address);
29+
30+
const init = async () => {
31+
const fromStorage = Storage.get(avatarSelector);
32+
if (fromStorage && fromStorage !== "") {
33+
setImage(fromStorage.image);
34+
return;
35+
}
36+
37+
setIsLoading(true);
38+
try {
39+
const tokenId = cairo.uint256(controller.address);
40+
let metadataRaw = await controller.account.callContract({
41+
contractAddress: avatarAddress,
42+
entrypoint: "token_uri",
43+
calldata: [tokenId.low, tokenId.high],
44+
});
45+
46+
const metadataString = stringFromByteArray(metadataRaw);
47+
const metadataJson = JSON.parse(metadataString);
48+
49+
Storage.set(avatarSelector, metadataJson);
50+
setImage(metadataJson.image);
51+
} catch (e) {
52+
// console.log(e);
53+
setImage("");
54+
}
55+
setIsLoading(false);
56+
};
57+
58+
init();
59+
}, [controller.address]);
60+
61+
return (
62+
<Box
63+
width={"48px"}
64+
height={"48px"}
65+
display={"flex"}
66+
flexShrink={0}
67+
position={"relative"}
68+
>
69+
{!isLoading && image !== "" ? <Image src={image} /> : <>{children}</>}
70+
{isLoading && (
71+
<Spinner position={"absolute"} size="sm" top={"16px"} left={"16px"} />
72+
)}
73+
</Box>
74+
);
75+
}

packages/keychain/src/components/layout/Container/Header/Banner.tsx

+37-33
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useControllerTheme } from "hooks/theme";
1414
import { useMemo } from "react";
1515
import { useLayoutVariant } from "../";
1616
import { TOP_BAR_HEIGHT } from "./TopBar";
17+
import { Avatar } from "components/avatar";
1718

1819
export type BannerProps = {
1920
Icon?: React.ComponentType<IconProps>;
@@ -106,23 +107,24 @@ export function Banner({ Icon, icon, title, description }: BannerProps) {
106107
/>
107108

108109
<HStack w="full" p={4} gap={4} minW={0}>
109-
{!!Icon ? (
110-
<Square size="44px" bg="solid.primary" borderRadius="md">
111-
<Icon boxSize={8} />
112-
</Square>
113-
) : !!icon ? (
114-
<Square size="44px" bg="solid.primary" borderRadius="md">
115-
{icon}
116-
</Square>
117-
) : (
118-
<Image
119-
src={theme.icon}
120-
boxSize="44px"
121-
alt="Controller Icon"
122-
borderRadius="md"
123-
/>
124-
)}
125-
110+
<Avatar>
111+
{!!Icon ? (
112+
<Square size="48px" bg="solid.primary" borderRadius="md">
113+
<Icon boxSize={8} />
114+
</Square>
115+
) : !!icon ? (
116+
<Square size="48px" bg="solid.primary" borderRadius="md">
117+
{icon}
118+
</Square>
119+
) : (
120+
<Image
121+
src={theme.icon}
122+
boxSize="48px"
123+
alt="Controller Icon"
124+
borderRadius="md"
125+
/>
126+
)}
127+
</Avatar>
126128
<VStack w="full" align="stretch" gap={1} minW={0}>
127129
<Text
128130
w="full"
@@ -160,22 +162,24 @@ export function Banner({ Icon, icon, title, description }: BannerProps) {
160162
/>
161163

162164
<HStack w="full" p={4} gap={4} minW={0}>
163-
{!!Icon ? (
164-
<Square size="44px" bg="solid.primary" borderRadius="md">
165-
<Icon boxSize={8} />
166-
</Square>
167-
) : !!icon ? (
168-
<Square size="44px" bg="solid.primary" borderRadius="md">
169-
{icon}
170-
</Square>
171-
) : (
172-
<Image
173-
src={theme.icon}
174-
boxSize="44px"
175-
alt="Controller Icon"
176-
borderRadius="md"
177-
/>
178-
)}
165+
<Avatar>
166+
{!!Icon ? (
167+
<Square size="48px" bg="solid.primary" borderRadius="md">
168+
<Icon boxSize={8} />
169+
</Square>
170+
) : !!icon ? (
171+
<Square size="48px" bg="solid.primary" borderRadius="md">
172+
{icon}
173+
</Square>
174+
) : (
175+
<Image
176+
src={theme.icon}
177+
boxSize="48px"
178+
alt="Controller Icon"
179+
borderRadius="md"
180+
/>
181+
)}
182+
</Avatar>
179183

180184
<VStack w="full" align="stretch" gap={1} minW={0}>
181185
<Text

packages/keychain/src/utils/selectors.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const selectors = {
44
[VERSION]: {
55
active: () => `@cartridge/active`,
66
account: (address: string) => `@cartridge/account/${address}`,
7+
avatar: (address: string) => `@cartridge/avatar/${address}`,
78
deployment: (address: string, chainId: string) =>
89
`@cartridge/deployment/${address}/${chainId}`,
910
admin: (address: string, origin: string) =>

0 commit comments

Comments
 (0)