Skip to content
Open
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
22 changes: 18 additions & 4 deletions src/features/main/components/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import { createAoContractClientForProcess } from "@/features/ao/lib/aoContractCl
import { AoWallet } from "@/features/ao/lib/aoWallet";
import { truncateAddress } from "@/features/arweave/lib/utils";
import { createChatClientForProcess } from "@/features/chat/contract/chatClient";
import { ProfileInfo } from "@/features/profile/contract/model";
import { ProfileAssets, ProfileInfo } from "@/features/profile/contract/model";
import { createProfileRegistryClientForProcess } from "@/features/profile/contract/profileRegistryClient";
import { Renderer } from "@/features/render/components/Renderer";
import { createRealityClientForProcess } from "@/features/reality/contract/realityClient";
import { mainMachine } from "../machines/mainMachine";
import { useMachine } from "@xstate/react";
import ProfileButton from "@/features/profile/components/ProfileButton";
import { createTrackingClientForProcess } from "@/features/tracking/contract/trackingClient";
import { createProfileClientForProcess } from "@/features/profile/contract/profileClient";

const profileRegistryProcessId = import.meta.env
.VITE_PROFILE_PROCESS_ID as string;
Expand All @@ -29,26 +30,38 @@ export default function Main({ wallet, disconnect, worldId }: MainProps) {
import.meta.env.VITE_TRACKING_TEST_PROCESS_ID,
);

const aoContractClientForProcess = createAoContractClientForProcess(wallet);

const profileClientForProcess = createProfileClientForProcess(wallet);

const realityClientForProcess = createRealityClientForProcess(wallet);

const realityClientBaseWorldId = realityClientForProcess(worldId || "");

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [current, send] = useMachine(mainMachine, {
input: {
initialContext: {
address: wallet.address,
profileRegistryClient,
profileClientForProcess,
aoContractClientForProcess,
realityClientBaseWorldId
},
},
});

const renderer = (profile?: {
profileId: string;
profileInfo: ProfileInfo;
assets: ProfileAssets;
}) => (
<Renderer
userAddress={wallet.address}
aoContractClientForProcess={createAoContractClientForProcess(wallet)}
aoContractClientForProcess={aoContractClientForProcess}
profileRegistryClient={profileRegistryClient}
trackingClient={trackingClient}
realityClientForProcess={createRealityClientForProcess(wallet)}
realityClientForProcess={realityClientForProcess}
chatClientForProcess={createChatClientForProcess(wallet)}
initialRealityId={worldId}
profileInfo={profile?.profileInfo}
Expand All @@ -68,7 +81,7 @@ export default function Main({ wallet, disconnect, worldId }: MainProps) {
{truncateAddress(wallet.address)}
</a>
</p>
<ProfileButton profileInfo={current.context.profileInfo} />
<ProfileButton profileInfo={current.context.profileInfo} assets={current.context.assets} />
<Button onClick={disconnect} size={"sm"} variant={"secondary"}>
Log out
</Button>
Expand All @@ -79,6 +92,7 @@ export default function Main({ wallet, disconnect, worldId }: MainProps) {
renderer({
profileId: current.context.profileId!,
profileInfo: current.context.profileInfo!,
assets: current.context.assets!,
})
) : (
renderer()
Expand Down
86 changes: 83 additions & 3 deletions src/features/main/machines/mainMachine.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { ProfileInfo } from "@/features/profile/contract/model";
import { AoContractClientForProcess, ReadArgs } from "@/features/ao/lib/aoContractClient";
import { Asset, DetailedAsset, ProfileAssets, ProfileInfo } from "@/features/profile/contract/model";
import { ProfileClientForProcess } from "@/features/profile/contract/profileClient";
import { ProfileRegistryClient } from "@/features/profile/contract/profileRegistryClient";
import { RealityClient } from "@/features/reality/contract/realityClient";
import { fromPromise, setup, assign } from "xstate";

type InitialContext = {
address: string;
profileRegistryClient: ProfileRegistryClient;
profileClientForProcess: ProfileClientForProcess;
aoContractClientForProcess: AoContractClientForProcess;
realityClientBaseWorldId: RealityClient;
};

export const mainMachine = setup({
Expand All @@ -15,6 +21,7 @@ export const mainMachine = setup({
context: {} as InitialContext & {
profileId?: string;
profileInfo?: ProfileInfo;
assets?: ProfileAssets;
},
},
guards: {
Expand All @@ -23,8 +30,9 @@ export const mainMachine = setup({
params: {
profileId?: string;
profileInfo?: ProfileInfo;
assets?: ProfileAssets;
},
) => params.profileId !== undefined && params.profileInfo !== undefined,
) => params.profileId !== undefined && params.profileInfo !== undefined && params.assets !== undefined,
},
actions: {
assignProfileIdAndInfo: assign(
Expand All @@ -33,10 +41,12 @@ export const mainMachine = setup({
params: {
profileId: string;
profileInfo: ProfileInfo;
assets: ProfileAssets;
},
) => ({
profileId: params.profileId,
profileInfo: params.profileInfo,
assets: params.assets
}),
),
},
Expand All @@ -48,13 +58,17 @@ export const mainMachine = setup({
input: {
address: string;
profileRegistryClient: ProfileRegistryClient;
bazarProfileClient: ProfileClientForProcess;
aoContractClientForProcess: AoContractClientForProcess;
realityClient: RealityClient;
};
}) => {
const { address, profileRegistryClient } = input;
const { address, profileRegistryClient, bazarProfileClient, aoContractClientForProcess, realityClient } = input;

const noProfile = {
profileId: undefined,
profileInfo: undefined,
assets: undefined,
};

const profiles =
Expand All @@ -68,9 +82,71 @@ export const mainMachine = setup({
]);
if (profileInfos.length === 0) return noProfile;

let profileClient = bazarProfileClient(primaryProfileId)

let responseData = await profileClient.grabProfileAssets()
// Assuming responseData.Data is a string, we first need to parse it
const dataObject = JSON.parse(responseData.Data);

// Now we can extract the Assets array from the parsed JSON
const assetsData = dataObject.Assets;

// Info Arguments
const args: ReadArgs = {
tags: [{ name: "Action", value: "Info" }],
};

// Fetch parameters
let parameters = await realityClient.readParameters();

console.log(parameters);

// Extract the whitelist (or null if it doesn't exist)
const whitelist = parameters["asset-whitelist"]?.Whitelist ?? null;

// Filter and process assets (if whitelist exists, filter by it; otherwise, process all assets)
let detailedAssets: DetailedAsset[] = await Promise.all(
assetsData
.filter((asset: any) => {
// If whitelist is null, don't filter (show everything); otherwise, filter by whitelist
return whitelist === null || whitelist.includes(asset.Id);
})
.map(async (asset: any) => {
let assetFormal: Asset = Asset.parse(asset);

let assetClient = aoContractClientForProcess(asset.Id);
let message = await assetClient.dryrunReadReplyOne(args);

let logoTag = message.Tags.find((tag) => tag.name === "Logo");
let denominationTag = message.Tags.find((tag) => tag.name === "Denomination");

if (denominationTag !== undefined) {
let denominator = 10 ** Number(denominationTag.value);
let quantityValue = Number(assetFormal.Quantity);
assetFormal.Quantity = (quantityValue / denominator).toString();
}

let detailedAsset;

if (logoTag === undefined) {
detailedAsset = DetailedAsset.parse({ asset: assetFormal, icon: assetFormal.Id });
} else {
detailedAsset = DetailedAsset.parse({ asset: assetFormal, icon: logoTag.value });
}

return detailedAsset;
})
);

// Parsing and validating the data with the ProfileAssets schema
const profileAssets = ProfileAssets.parse(detailedAssets);

console.log("Validated Profile Assets:", profileAssets);

return {
profileId: primaryProfileId,
profileInfo: profileInfos[0],
assets: profileAssets,
};
},
),
Expand All @@ -96,6 +172,9 @@ export const mainMachine = setup({
input: ({ context }) => ({
address: context.address,
profileRegistryClient: context.profileRegistryClient,
bazarProfileClient: context.profileClientForProcess,
aoContractClientForProcess: context.aoContractClientForProcess,
realityClient: context.realityClientBaseWorldId
}),

onDone: [
Expand All @@ -110,6 +189,7 @@ export const mainMachine = setup({
params: ({ event }) => ({
profileId: event.output.profileId!,
profileInfo: event.output.profileInfo!,
assets: event.output.assets!,
}),
},
},
Expand Down
23 changes: 23 additions & 0 deletions src/features/profile/components/AssetCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { fetchUrl } from '@/features/arweave/lib/arweave';
import { DetailedAsset } from '../contract/model'; // Adjust the path as needed

interface AssetCardProps {
asset?: DetailedAsset; // Make asset optional
}

export default function AssetCard({ asset }: AssetCardProps) {
if (!asset) return null; // Return nothing if no asset

return (
<div className="relative w-full h-full object-cover">
<img src={fetchUrl(asset.icon)} alt={asset.asset.Id}
className="inset-0 w-full h-full object-cover" // Add these classes
/>
{asset.asset.Quantity && parseInt(asset.asset.Quantity) > 1 && (
<div className="absolute bottom-6 right-5 bg-gray-50 text-black text-xs px-2 py-1 rounded-full">
{asset.asset.Quantity}
</div>
)}
</div>
);
}
23 changes: 23 additions & 0 deletions src/features/profile/components/ProfileAssetsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { DetailedAsset } from '../contract/model'; // Adjust the path as needed
import AssetCard from './AssetCard';

interface ProfileAssetsDisplayProps {
assets: DetailedAsset[];
}

export default function ProfileAssetsDisplay({ assets }: ProfileAssetsDisplayProps) {
const totalCells = Math.max(4, Math.ceil(assets.length / 4) * 4);

return (
<div className={`grid grid-cols-4 gap-4 ${assets.length === 0 ? 'p-2' : ''}`}>
{[...Array(totalCells)].map((_, index) => (
<div
key={index}
className="border border-gray-300 aspect-square" // Use Tailwind's aspect-square utility
>
{assets[index] && <AssetCard asset={assets[index]} />}
</div>
))}
</div>
);
}
7 changes: 4 additions & 3 deletions src/features/profile/components/ProfileButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ProfileInfo } from "../contract/model";
import { ProfileAssets, ProfileInfo } from "../contract/model";
import ProfileDetailsDropdown from "./ProfileDetailsDropdown";
import {
Card,
Expand All @@ -16,17 +16,18 @@ import ProfileImage from "./ProfileImage";

interface ProfileButtonProps {
profileInfo?: ProfileInfo;
assets?: ProfileAssets;
}

export default function ProfileButton({ profileInfo }: ProfileButtonProps) {
export default function ProfileButton({ profileInfo, assets }: ProfileButtonProps) {
return (
<Popover>
<PopoverTrigger>
<ProfileImage profileImage={profileInfo?.ProfileImage} size="small" />
</PopoverTrigger>
<PopoverContent align="end">
{profileInfo ? (
<ProfileDetailsDropdown profileInfo={profileInfo} />
<ProfileDetailsDropdown profileInfo={profileInfo} profileAssets={assets} />
) : (
<Card>
<CardHeader>
Expand Down
11 changes: 9 additions & 2 deletions src/features/profile/components/ProfileDetailsDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { ProfileInfo } from "../contract/model";
import { ProfileAssets, ProfileInfo } from "../contract/model";
import ProfileImage from "./ProfileImage";
import ProfileAssetsDisplay from './ProfileAssetsDisplay'; // Import your new component

interface ProfileDetailsDropdownProps {
profileInfo: ProfileInfo;
profileAssets?: ProfileAssets;
}

export default function ProfileDetailsDropdown({
profileInfo,
profileAssets
}: ProfileDetailsDropdownProps) {
const hasDescription =
profileInfo.Description && profileInfo.Description != "";
Expand All @@ -32,8 +35,12 @@ export default function ProfileDetailsDropdown({
) : (
<p className="text-center text-gray-500 italic">No bio</p>
)}
<div className="mt-4">
<h3 className="text-center">Assets</h3>
<ProfileAssetsDisplay assets={profileAssets || []} />
</div>
<p className="mt-4 text-sm text-gray-400 italic">
Edit your profile on{" "}
Edit your profile and view all your assets not just World specific ones on:{" "}
<a
href="https://ao-bazar.arweave.dev/#/"
target="_blank"
Expand Down
18 changes: 18 additions & 0 deletions src/features/profile/contract/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,21 @@ export const ProfileInfoUpdate = ProfileInfoCreate.omit({
DateCreated: true,
}).partial();
export type ProfileInfoUpdate = z.infer<typeof ProfileInfoUpdate>;

// Define the Asset schema
export const Asset = z.object({
Quantity: z.string(),
Id: z.string(),
});
export type Asset = z.infer<typeof Asset>;

// Define the DetailedAsset schema with an optional icon
export const DetailedAsset = z.object({
asset: Asset,
icon: z.string(), // Making icon optional
});
export type DetailedAsset = z.infer<typeof DetailedAsset>;

// Define the ProfileAssets schema as an array of DetailedAssets
export const ProfileAssets = z.array(DetailedAsset);
export type ProfileAssets = z.infer<typeof ProfileAssets>;
Loading