diff --git a/apps/pwa/package.json b/apps/pwa/package.json index c5ed112ca..e30da4ee2 100644 --- a/apps/pwa/package.json +++ b/apps/pwa/package.json @@ -52,9 +52,9 @@ "react-transition-group": "^4.4.5", "react-use-websocket": "^4.8.1", "starknet": "6.9.0", - "starknetkit-next": "npm:starknetkit@2.3.3", + "starknetkit-next": "npm:starknetkit@^2.6.1", "viem": "~2.21.2", - "wagmi": "^2.12.8", + "wagmi": "^2.13.4", "zod": "^3.23.8", "zustand": "^4.5.2", "afk_react_sdk": "workspace:*", diff --git a/packages/afk_sdk/src/usePlacePixel.ts b/packages/afk_sdk/src/usePlacePixel.ts index abd404aa0..84e6e2ce9 100644 --- a/packages/afk_sdk/src/usePlacePixel.ts +++ b/packages/afk_sdk/src/usePlacePixel.ts @@ -1,5 +1,5 @@ import { useMutation } from '@tanstack/react-query'; -import { Account, num, RPC } from 'starknet'; +import { Account, AccountInterface, num, RPC } from 'starknet'; // Detect Telegram context const isTelegramContext = () => @@ -41,10 +41,12 @@ interface ExecuteContractActionResult { export const executeContractAction = async ({ account, callProps, + wallet, options = {}, }: { - account: any; - callProps: ContractCallProps; + account: AccountInterface; + callProps: any; + wallet: any; options: ExecuteContractActionOptions; }): Promise => { const { version = 3, argentTMA } = options; @@ -54,16 +56,9 @@ export const executeContractAction = async ({ ? ContractActionContextType.Telegram : ContractActionContextType.Expo; - // Prepare the call for execution - const myCall = { - contractAddress: callProps.contractAddress, - entrypoint: callProps.method, - calldata: callProps.calldata, - }; - try { // Estimate fees (same for both contexts) - const estimatedFee = await account.estimateInvokeFee([myCall], { version }); + const estimatedFee = await account.estimateInvokeFee([callProps], { version }); // Apply fee multiplier (default to 1.5x if not specified) const feeMultiplier = callProps.feeMultiplier || 1.5; @@ -91,10 +86,21 @@ export const executeContractAction = async ({ version, maxFee, }; - - // Execute the transaction using account.execute() - const { transaction_hash } = await account.execute(myCall, transactionOptions); - + // Execute the transaction using account.execute() or invoke with wallet since we using sessions + const { transaction_hash } = wallet + ? await wallet.request({ + type: 'wallet_addInvokeTransaction', + params: { + calls: [ + { + calldata: callProps.calldata, + contract_address: callProps.contractAddress, + entry_point: callProps.entrypoint, + }, + ], + }, + }) + : await account.execute([callProps], transactionOptions); // Wait for transaction receipt let receipt; if (contextType === ContractActionContextType.Telegram) { @@ -111,6 +117,7 @@ export const executeContractAction = async ({ receipt, }; } catch (error) { + console.log(error, 'err'); return Promise.reject(error); } }; diff --git a/packages/pixel_ui/package.json b/packages/pixel_ui/package.json index 3f0e41add..b4c93dce1 100644 --- a/packages/pixel_ui/package.json +++ b/packages/pixel_ui/package.json @@ -19,7 +19,7 @@ "starknet": "^6.9.0", "@argent/x-sessions": "^6.7.4", "@argent/x-shared": "^1.32.1", - "starknetkit-next": "npm:starknetkit@2.3.3", + "starknetkit-next": "npm:starknetkit@^2.6.1", "common": "workspace:*", "afk_react_sdk": "workspace:*", "afk_sdk": "workspace:*" @@ -58,4 +58,4 @@ "eslint-plugin-react-hooks": "^4.6.0", "prettier": "^3.2.5" } -} +} \ No newline at end of file diff --git a/packages/pixel_ui/src/App.tsx b/packages/pixel_ui/src/App.tsx index d2b913fbb..45bb5bdd4 100644 --- a/packages/pixel_ui/src/App.tsx +++ b/packages/pixel_ui/src/App.tsx @@ -73,16 +73,17 @@ function App({ contractAddress, usernameAddress, nftCanvasAddress }: IApp) { //--> Connect Argent useConnectArgent() - const { - connectWallet, + const { + connectWallet, startSession, account, + wallet, address, queryAddress, setConnected, isSessionable, disconnectWallet, - usingSessionKeys + usingSessionKeys } = useWalletStore() @@ -159,7 +160,7 @@ function App({ contractAddress, usernameAddress, nftCanvasAddress }: IApp) { } setHost(response.data.host); setEndTimestamp(response.data.endTime); - } catch(e) { + } catch (e) { console.error(e); } }; @@ -496,7 +497,8 @@ function App({ contractAddress, usernameAddress, nftCanvasAddress }: IApp) { setFactionPixelsData(factionPixelsResponse.data); } catch (e) { console.error(e); - }} + } + } fetchFactionPixelsEndpoint(); }, [queryAddress]); @@ -516,9 +518,9 @@ function App({ contractAddress, usernameAddress, nftCanvasAddress }: IApp) { try { let pixelInfo = await fetchWrapper( - `get-pixel-info&position=${x+y*canvasConfig.canvas.width}` + `get-pixel-info&position=${x + y * canvasConfig.canvas.width}` ); - console.log("pixelInfo data",pixelInfo) + console.log("pixelInfo data", pixelInfo) } catch (e) { console.error(e); } @@ -603,7 +605,8 @@ function App({ contractAddress, usernameAddress, nftCanvasAddress }: IApp) { setChainFaction(chainFactionResponse.data[0]); } catch (e) { console.error(e); - }} + } + } async function fetchUserFactions() { try { let userFactionsResponse = await fetchWrapper( @@ -699,6 +702,7 @@ function App({ contractAddress, usernameAddress, nftCanvasAddress }: IApp) { colorPixel={colorPixel} address={address} account={account} + wallet={wallet} artPeaceContract={artPeaceContract} colors={colors} canvasRef={canvasRef} @@ -746,10 +750,10 @@ function App({ contractAddress, usernameAddress, nftCanvasAddress }: IApp) { /> {(!isMobile || activeTab === tabs[0]) && (
- logo - -
AFK
-
+ logo + +
AFK
+
)}
@@ -894,7 +899,8 @@ function App({ contractAddress, usernameAddress, nftCanvasAddress }: IApp) { isMobile={isMobile} clearAll={clearAll} account={account} - + wallet={wallet} + /> )} {isFooterSplit && !footerExpanded && ( diff --git a/packages/pixel_ui/src/canvas/CanvasContainer.js b/packages/pixel_ui/src/canvas/CanvasContainer.js index 2f455a4ca..116fd8d86 100644 --- a/packages/pixel_ui/src/canvas/CanvasContainer.js +++ b/packages/pixel_ui/src/canvas/CanvasContainer.js @@ -1,3 +1,5 @@ + + import './CanvasContainer.css'; import React, { useEffect, useRef, useState } from 'react'; @@ -8,9 +10,9 @@ import ExtraPixelsCanvas from './ExtraPixelsCanvas.js'; import NFTSelector from './NFTSelector.js'; import TemplateCreationOverlay from './TemplateCreationOverlay.js'; import TemplateOverlay from './TemplateOverlay.js'; -import { useContractAction} from "afk_sdk"; -import {ART_PEACE_ADDRESS} from "common" -import {CallData} from 'starknet'; +import { useContractAction } from "afk_sdk"; +import { ART_PEACE_ADDRESS } from "common" +import MetadataView from './metadata/Metadata'; const CanvasContainer = (props) => { @@ -34,6 +36,8 @@ const CanvasContainer = (props) => { const [isErasing, setIsErasing] = useState(false); + const [showMetadataForm, setShowMetaDataForm] = useState(false) + const handlePointerDown = (e) => { // TODO: Require over canvas? if (!props.isEraserMode) { @@ -202,6 +206,7 @@ const CanvasContainer = (props) => { }; const pixelSelect = async (x, y) => { + // Clear selection if clicking the same pixel if ( props.selectedColorId === -1 && @@ -233,37 +238,28 @@ const CanvasContainer = (props) => { }; //Pixel Call Hook - const { mutate: mutatePlacePixel} = useContractAction() + const { mutateAsync: mutatePlacePixel } = useContractAction() + const placePixelCall = async (position, color, now) => { - // if (devnetMode) return; - // if (!props.address || !props.artPeaceContract) return; - if (!props.address || !props.artPeaceContract || !props.account) return; - const timestamp = Math.floor(Date.now() / 1000); - mutatePlacePixel({ - account: props.account, - callProps:{ - calldata: CallData.compile({ - position, - color, - now: timestamp, - }), - contractAddress:ART_PEACE_ADDRESS?.['0x534e5f5345504f4c4941'], - method:"place_pixel" - } - }) - - // console.log('user connected', props.account?.address); - // const pixelCalldata = props.artPeaceContract.populate('place_pixel', { - // pos: position, - // color: color, - // now: now - // }); - - - // console.log(pixelCalldata,"pixel") - // const result = await props.account.execute([pixelCalldata]); - // console.log(result,"res") + + // if (devnetMode) return; + // if (!props.address || !props.artPeaceContract) return; + if (!props.address || !props.artPeaceContract || !props.account) return; + + mutatePlacePixel({ + account: props.account, + wallet: props.wallet, + callProps: { + calldata: [position, color, now], + contractAddress: ART_PEACE_ADDRESS?.['0x534e5f5345504f4c4941'], + entrypoint: "place_pixel" + } + }, { + onError() { + setShowMetaDataForm(false) + } + }) }; @@ -272,6 +268,11 @@ const CanvasContainer = (props) => { return; } + //Show Metadata Form on Pixel Clicked and pixel selectMode + if (props.selectedColorId !== -1) { + setShowMetaDataForm(true); + } + const canvas = props.canvasRef.current; const rect = canvas.getBoundingClientRect(); const x = Math.floor(((e.clientX - rect.left) / (rect.right - rect.left)) * width); @@ -320,7 +321,7 @@ const CanvasContainer = (props) => { // if (!devnetMode) { props.setSelectedColorId(-1); props.colorPixel(position, colorId); - await placePixelCall(position, colorId, timestamp); + await placePixelCall(position,colorId,timestamp); props.clearPixelSelection(); props.setLastPlacedTime(timestamp * 1000); // return; @@ -468,53 +469,43 @@ const CanvasContainer = (props) => { ]); return ( -
+ <> + setShowMetaDataForm(false)} showMeta={showMetadataForm} />
- {props.pixelSelectedMode && ( -
+
+ {props.pixelSelectedMode && (
-
- )} - - {props.availablePixels > 0 && ( - +
+
+ )} + { colors={props.colors} pixelClicked={pixelClicked} /> - )} - {props.templateOverlayMode && props.overlayTemplate && ( - - )} - {props.templateCreationMode && ( - - )} - {props.nftMintingMode && ( - - )} + {props.availablePixels > 0 && ( + + )} + {props.templateOverlayMode && props.overlayTemplate && ( + + )} + {props.templateCreationMode && ( + + )} + {props.nftMintingMode && ( + + )} +
-
+ ); }; diff --git a/packages/pixel_ui/src/canvas/metadata/Metadata.tsx b/packages/pixel_ui/src/canvas/metadata/Metadata.tsx new file mode 100644 index 000000000..b2eab9eba --- /dev/null +++ b/packages/pixel_ui/src/canvas/metadata/Metadata.tsx @@ -0,0 +1,93 @@ +import { useState } from 'react'; +import './metadataForm.css' +type IProps = { + showMeta: boolean; + closeMeta: () => void; +} +type FormData = { + twitter: string; + nostr: string; + ips: string; +} + +export default function MetadataForm({ showMeta, closeMeta }: IProps) { + const [formData, setFormData] = useState({ + twitter: '', + nostr: '', + ips: '' + }) + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target + setFormData(prevData => ({ + ...prevData, + [name]: value + })) + } + + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + // Handle form submission logic here + console.log('Form submitted'); + setFormData({ twitter: '', nostr: '', ips: '' }) + closeMeta() + } + + return ( +
+
+
+ +

Add Pixel Metadata

+
+ + +
+
+ + +
+
+ + +
+ +
+
+
+ ) +} + diff --git a/packages/pixel_ui/src/canvas/metadata/metadataForm.css b/packages/pixel_ui/src/canvas/metadata/metadataForm.css new file mode 100644 index 000000000..181000a91 --- /dev/null +++ b/packages/pixel_ui/src/canvas/metadata/metadataForm.css @@ -0,0 +1,124 @@ +.metadata-form-container { + position: fixed; + bottom: 1rem; + right: 1rem; + z-index: 9999; + } + + .metadata-form-toggle { + background-color: #3b82f6; + color: white; + font-weight: bold; + padding: 0.5rem 1rem; + border: none; + border-radius: 0.25rem; + cursor: pointer; + transition: background-color 0.3s ease; + } + + .metadata-form-toggle:hover { + background-color: #2563eb; + } + + .metadata-form { + position: absolute; + z-index: 9999; + bottom: 100%; + right: 0; + margin-bottom: 0.5rem; + width: 30rem; + /* background-color: white; */ + border-radius: 0.8rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease-in-out; + opacity: 0; + transform: translateY(1rem); + pointer-events: none; + border: 1px solid rgba(0, 0, 0, 0.5); + background: linear-gradient( + 45deg, + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0.9) + ); + box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.3); + border: 1px solid rgba(0, 0, 0, 0.3); + } + + .metadata-form.open { + opacity: 1; + transform: translateY(0); + pointer-events: auto; + } + + .metadata-form-content { + padding: 1rem; + } + + .metadata-form-close { + position: absolute; + top: 0.5rem; + right: 0.5rem; + background: none; + border: none; + color: #6b7280; + cursor: pointer; + transition: color 0.3s ease; + } + + .metadata-form-close:hover { + color: #374151; + } + + .metadata-form h2 { + font-size: 1.125rem; + font-weight: 600; + margin-bottom: 1rem; + color: #374151; + } + + .metadata-form-field { + margin-bottom: 1rem; + } + + .metadata-form-field label { + display: block; + font-size: 0.775rem; + font-weight: 500; + color: #374151; + margin-bottom: 0.25rem; + } + + .metadata-form-field input { + width: 100%; + padding: 0.5rem; + border: 1px solid #d1d5db; + border-radius: 0.25rem; + font-size: 0.875rem; + color: black; + background-color: white; + } + + .metadata-form-field input:focus { + outline: none; + border-color: #3b82f6; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); + } + + .metadata-form-submit { + width: 100%; + background-color: #3b82f6; + color: white; + font-size: 1rem; + font-weight: bold; + padding: 0.5rem 1rem; + border: none; + border-radius: 0.25rem; + cursor: pointer; + transition: background-color 0.3s ease; + } + + .metadata-form-submit:hover { + background-color: #2563eb; + } + + \ No newline at end of file diff --git a/packages/pixel_ui/src/tabs/account/Account.css b/packages/pixel_ui/src/tabs/account/Account.css index 1025ee410..323ecfd94 100644 --- a/packages/pixel_ui/src/tabs/account/Account.css +++ b/packages/pixel_ui/src/tabs/account/Account.css @@ -233,3 +233,12 @@ padding: 0; margin: 0.5rem 0.5rem 0 0.5rem; } +.Account__username__input{ + width: 100%; + padding: 0.5rem; + border: 1px solid #d1d5db; + border-radius: 0.25rem; + font-size: 0.875rem; + color: black; + background-color: white; +}