From 55a349fe1c6d1b2ad69249baa5ed7a807a6b8a58 Mon Sep 17 00:00:00 2001 From: nikhlu07 Date: Mon, 16 Feb 2026 09:38:34 +0530 Subject: [PATCH 1/2] feat(web): enable real contract interactions (create/join) on Testnet --- loopin-web/.env | 2 +- loopin-web/.env.example | 10 +++ loopin-web/README.md | 73 ------------------- .../dashboard/ActiveSessionsList.tsx | 24 ++++-- .../dashboard/DashboardActionGrid.tsx | 73 ++++++++++++++++++- loopin-web/src/lib/contracts.ts | 59 +++++++++++++++ loopin-web/src/lib/wallet-utils.ts | 45 ++++++++---- loopin-web/src/types/database.ts | 44 +++++++++++ 8 files changed, 232 insertions(+), 98 deletions(-) create mode 100644 loopin-web/.env.example delete mode 100644 loopin-web/README.md create mode 100644 loopin-web/src/lib/contracts.ts create mode 100644 loopin-web/src/types/database.ts diff --git a/loopin-web/.env b/loopin-web/.env index b7b484c25..2f761483b 100644 --- a/loopin-web/.env +++ b/loopin-web/.env @@ -1,4 +1,4 @@ VITE_API_URL=https://loopin-1-77vi.onrender.com/api -VITE_CONTRACT_ADDRESS=ST36BMEQDCRCKYF8HPPDMN1BCSY6TR2NG0BZSQPYG +VITE_CONTRACT_ADDRESS=ST4XJYMD9FCF3PZXAKFRTSXX9CHRSEDS6BJ4ASFQ VITE_CONTRACT_NAME=loopin-game VITE_NETWORK=testnet diff --git a/loopin-web/.env.example b/loopin-web/.env.example new file mode 100644 index 000000000..cafc67176 --- /dev/null +++ b/loopin-web/.env.example @@ -0,0 +1,10 @@ +# Backend API URL +VITE_API_URL=http://localhost:3000/api + +# Wallet Connect (for future use) +VITE_WALLET_CONNECT_PROJECT_ID= + +# Smart Contract +VITE_CONTRACT_ADDRESS=ST36BMEQDCRCKYF8HPPDMN1BCSY6TR2NG0BZSQPYG +VITE_CONTRACT_NAME=loopin-game +VITE_NETWORK=testnet diff --git a/loopin-web/README.md b/loopin-web/README.md deleted file mode 100644 index 70b7c82ad..000000000 --- a/loopin-web/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# Welcome to your Lovable project - -## Project info - -**URL**: https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID - -## How can I edit this code? - -There are several ways of editing your application. - -**Use Lovable** - -Simply visit the [Lovable Project](https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID) and start prompting. - -Changes made via Lovable will be committed automatically to this repo. - -**Use your preferred IDE** - -If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable. - -The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating) - -Follow these steps: - -```sh -# Step 1: Clone the repository using the project's Git URL. -git clone - -# Step 2: Navigate to the project directory. -cd - -# Step 3: Install the necessary dependencies. -npm i - -# Step 4: Start the development server with auto-reloading and an instant preview. -npm run dev -``` - -**Edit a file directly in GitHub** - -- Navigate to the desired file(s). -- Click the "Edit" button (pencil icon) at the top right of the file view. -- Make your changes and commit the changes. - -**Use GitHub Codespaces** - -- Navigate to the main page of your repository. -- Click on the "Code" button (green button) near the top right. -- Select the "Codespaces" tab. -- Click on "New codespace" to launch a new Codespace environment. -- Edit files directly within the Codespace and commit and push your changes once you're done. - -## What technologies are used for this project? - -This project is built with: - -- Vite -- TypeScript -- React -- shadcn-ui -- Tailwind CSS - -## How can I deploy this project? - -Simply open [Lovable](https://lovable.dev/projects/REPLACE_WITH_PROJECT_ID) and click on Share -> Publish. - -## Can I connect a custom domain to my Lovable project? - -Yes, you can! - -To connect a domain, navigate to Project > Settings > Domains and click Connect Domain. - -Read more here: [Setting up a custom domain](https://docs.lovable.dev/features/custom-domain#custom-domain) diff --git a/loopin-web/src/components/dashboard/ActiveSessionsList.tsx b/loopin-web/src/components/dashboard/ActiveSessionsList.tsx index ac1f91e2b..1437536a1 100644 --- a/loopin-web/src/components/dashboard/ActiveSessionsList.tsx +++ b/loopin-web/src/components/dashboard/ActiveSessionsList.tsx @@ -1,15 +1,26 @@ import React from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { Users, Clock, ArrowUpRight } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { SlideUp, StaggerContainer } from '@/components/animation/MotionWrapper'; import { Game } from '@/lib/api'; +import { joinGame } from '@/lib/contracts'; interface ActiveSessionsListProps { activeSessions: Game[]; } const ActiveSessionsList: React.FC = ({ activeSessions }) => { + const navigate = useNavigate(); + + const handleJoin = (session: Game) => { + // Parse ID as int because contract expects uint + // Ensure entry_fee is number + joinGame(parseInt(session.id), session.entry_fee, () => { + navigate(`/game/${session.id}`); + }); + }; + return (
@@ -55,11 +66,12 @@ const ActiveSessionsList: React.FC = ({ activeSessions
{session.entry_fee} STX
- - - +
diff --git a/loopin-web/src/components/dashboard/DashboardActionGrid.tsx b/loopin-web/src/components/dashboard/DashboardActionGrid.tsx index f80bd1593..ec52e3500 100644 --- a/loopin-web/src/components/dashboard/DashboardActionGrid.tsx +++ b/loopin-web/src/components/dashboard/DashboardActionGrid.tsx @@ -1,7 +1,10 @@ -import React from 'react'; -import { Gift, Zap, X } from 'lucide-react'; +import React, { useState } from 'react'; +import { Gift, Zap, X, MapPin } from 'lucide-react'; import { Dialog, DialogContent, DialogTrigger, DialogClose } from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { createGame } from '@/lib/contracts'; import { SlideUp } from '@/components/animation/MotionWrapper'; import PowerupShop from './PowerupShop'; import DailyRewardCard from './DailyRewardCard'; @@ -19,8 +22,72 @@ const DashboardActionGrid: React.FC = ({ onBalanceUpdate, onRewardClaimed }) => { + const [gameType, setGameType] = useState("Standard"); + const [maxPlayers, setMaxPlayers] = useState(10); + + const handleCreateGame = () => { + createGame(gameType, maxPlayers); + }; + return ( -
+
+ {/* Create Game Trigger */} + + +
+ +
+
+ +
+
+ New +
+
+
+

Start Grid

+

Create a new game.

+
+
+
+
+ +
+ + + + +

Launch Grid

+ +
+
+ + setGameType(e.target.value)} + className="font-bold border-2 border-black h-12 rounded-xl" + /> +
+
+ + setMaxPlayers(parseInt(e.target.value))} + className="font-bold border-2 border-black h-12 rounded-xl" + /> +
+ + +
+
+
+
{/* Daily Reward Trigger */} diff --git a/loopin-web/src/lib/contracts.ts b/loopin-web/src/lib/contracts.ts new file mode 100644 index 000000000..80a082d37 --- /dev/null +++ b/loopin-web/src/lib/contracts.ts @@ -0,0 +1,59 @@ +import { openContractCall } from '@stacks/connect'; +import { StacksTestnet } from '@stacks/network'; +import { Cl, PostConditionMode } from '@stacks/transactions'; + +const network = new StacksTestnet(); +const contractAddress = import.meta.env.VITE_CONTRACT_ADDRESS; +const contractName = import.meta.env.VITE_CONTRACT_NAME || 'loopin-game'; + +export const createGame = (initialGameType: string, initialMaxPlayers: number) => { + if (!contractAddress) { + alert("Contract address not set in .env!"); + return; + } + + const options = { + contractAddress, + contractName, + functionName: 'create-game', + functionArgs: [ + Cl.stringAscii(initialGameType), + Cl.uint(initialMaxPlayers) + ], + network, + postConditionMode: PostConditionMode.Allow, // Simplest for now + onFinish: (data: any) => { + console.log('Transaction broadcasted:', data); + alert(`Game Creation Transaction Broadcasted! TxId: ${data.txId}`); + // Ideally we reload window or poll + setTimeout(() => window.location.reload(), 2000); + }, + }; + + openContractCall(options); +}; + +export const joinGame = (gameId: number, entryFee: number, onSuccess?: () => void) => { + if (!contractAddress) { + alert("Contract address not set in .env!"); + return; + } + + const options = { + contractAddress, + contractName, + functionName: 'join-game', + functionArgs: [ + Cl.uint(gameId) + ], + network, + postConditionMode: PostConditionMode.Allow, // Allow STX transfer + onFinish: (data: any) => { + console.log('Transaction broadcasted:', data); + // alert(`Joined Game! TxId: ${data.txId}`); + if (onSuccess) onSuccess(); + }, + }; + + openContractCall(options); +}; diff --git a/loopin-web/src/lib/wallet-utils.ts b/loopin-web/src/lib/wallet-utils.ts index 958a81f54..d2cd25a39 100644 --- a/loopin-web/src/lib/wallet-utils.ts +++ b/loopin-web/src/lib/wallet-utils.ts @@ -43,9 +43,7 @@ export const connectWalletDesktop = ( userSession: UserSession, onFinish?: () => void ) => { - console.log('[Wallet] Starting authentication...'); - console.log('[Wallet] Is mobile?', isMobileDevice()); - console.log('[Wallet] User agent:', navigator.userAgent); + console.log('[Wallet] 🚀 Starting authentication...'); authenticate({ appDetails: { @@ -53,29 +51,46 @@ export const connectWalletDesktop = ( icon: window.location.origin + "/logo.svg", }, onFinish: (data: any) => { - console.log('[Wallet] onFinish called with data:', data); + console.log('[Wallet] ✅ Authentication successful!'); + console.log('[Wallet] Data received:', data); - // Save wallet address to localStorage + // CRITICAL: Save wallet address IMMEDIATELY try { + // Method 1: Try to get from userSession if (userSession.isUserSignedIn()) { const userData = userSession.loadUserData(); const walletAddress = userData.profile.stxAddress.mainnet; - console.log('[Wallet] Saving wallet address:', walletAddress); + console.log('[Wallet] ✅ Got wallet from session:', walletAddress); localStorage.setItem('loopin_wallet', walletAddress); + + // Trigger storage event for Header to detect + window.dispatchEvent(new StorageEvent('storage', { + key: 'loopin_wallet', + newValue: walletAddress, + url: window.location.href + })); + + console.log('[Wallet] ✅ Wallet saved to localStorage'); + + // Call callback if provided + if (onFinish) { + onFinish(); + } + + // Force page reload to update all components + setTimeout(() => { + console.log('[Wallet] 🔄 Reloading page...'); + window.location.reload(); + }, 500); + } else { + console.error('[Wallet] ❌ User not signed in after authentication'); } } catch (error) { - console.error('[Wallet] Error saving wallet address:', error); - } - - if (onFinish) { - onFinish(); - } else { - // Reload to update UI - window.location.reload(); + console.error('[Wallet] ❌ Error saving wallet:', error); } }, onCancel: () => { - console.log('[Wallet] User cancelled connection'); + console.log('[Wallet] ❌ User cancelled connection'); }, userSession, }); diff --git a/loopin-web/src/types/database.ts b/loopin-web/src/types/database.ts new file mode 100644 index 000000000..ccfbccdd8 --- /dev/null +++ b/loopin-web/src/types/database.ts @@ -0,0 +1,44 @@ +// Player Profile Type +export interface PlayerProfile { + id: string; + wallet_address: string; + username: string; + avatar_seed: string; + level: number; + joined_at: string; +} + +// Player Stats Type +export interface PlayerStats { + player_id: string; + total_area: number; + games_played: number; + games_won: number; + total_earnings: number; + current_streak: number; +} + +// Game Session Type +export interface GameSession { + id: string; + game_type: string; + status: string; + max_players: number; + entry_fee: number; + prize_pool: number; + creator_wallet: string; + created_at: string; + start_time?: string; + end_time?: string; +} + +// Game Participant Type +export interface GameParticipant { + id: string; + game_id: string; + player_id: string; + area_captured: number; + rank: number; + prize_won: number; + joined_at: string; +} From 8ecde289fb80fab21175116e1a7ed97c0c3661f4 Mon Sep 17 00:00:00 2001 From: nikhlu07 Date: Tue, 17 Feb 2026 13:01:28 +0530 Subject: [PATCH 2/2] Fix build: Replace StacksTestnet class with STACKS_TESTNET constant --- loopin-web/src/lib/contracts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loopin-web/src/lib/contracts.ts b/loopin-web/src/lib/contracts.ts index 80a082d37..abb15928a 100644 --- a/loopin-web/src/lib/contracts.ts +++ b/loopin-web/src/lib/contracts.ts @@ -1,8 +1,8 @@ import { openContractCall } from '@stacks/connect'; -import { StacksTestnet } from '@stacks/network'; +import { STACKS_TESTNET } from '@stacks/network'; import { Cl, PostConditionMode } from '@stacks/transactions'; -const network = new StacksTestnet(); +const network = STACKS_TESTNET; const contractAddress = import.meta.env.VITE_CONTRACT_ADDRESS; const contractName = import.meta.env.VITE_CONTRACT_NAME || 'loopin-game';