From 0f8722a46cbdf2282331f59f6d2adf8743e9f16f Mon Sep 17 00:00:00 2001 From: Ishan Lakhwani Date: Sun, 22 Sep 2024 02:22:48 +0800 Subject: [PATCH] web app basic working --- package.json | 4 +- packages/chrome-extension/contentScript.js | 4 +- packages/chrome-extension/styles.css | 26 ++-- packages/nextjs/app/page.tsx | 58 ++++++-- packages/nextjs/components/Footer.tsx | 21 +-- packages/nextjs/components/Header.tsx | 22 ++- .../inspectorAiComponents/ReviewPage.tsx | 138 ++++++++++++++++++ .../nextjs/contracts/deployedContracts.ts | 2 +- .../hooks/scaffold-eth/useInspectorAI.ts | 68 +++++++++ packages/nextjs/public/logo_with_name.png | Bin 0 -> 8571 bytes packages/nextjs/styles/globals.css | 39 +++-- packages/nextjs/tailwind.config.js | 14 ++ 12 files changed, 322 insertions(+), 74 deletions(-) create mode 100644 packages/nextjs/components/inspectorAiComponents/ReviewPage.tsx create mode 100644 packages/nextjs/hooks/scaffold-eth/useInspectorAI.ts create mode 100644 packages/nextjs/public/logo_with_name.png diff --git a/package.json b/package.json index 3667467..9605439 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ }, "packageManager": "yarn@3.2.3", "devDependencies": { + "@types/canvas-confetti": "^1.6.4", "husky": "^8.0.1", "lint-staged": "^13.0.3" }, @@ -44,6 +45,7 @@ "node": ">=18.17.0" }, "dependencies": { - "axios": "^1.7.7" + "axios": "^1.7.7", + "canvas-confetti": "^1.9.3" } } diff --git a/packages/chrome-extension/contentScript.js b/packages/chrome-extension/contentScript.js index 36f3eec..891fe8c 100644 --- a/packages/chrome-extension/contentScript.js +++ b/packages/chrome-extension/contentScript.js @@ -22,9 +22,9 @@ function injectAiAuditButton() { const aiAuditButton = document.createElement('button'); aiAuditButton.textContent = '✨ AI Audit'; aiAuditButton.style.cssText = ` - background-color: #6A0DAD; + background-color: #334155; border: none; - color: white; + color: #F8FAFC; padding: 5px 10px; font-size: 12px; margin-left: 10px; diff --git a/packages/chrome-extension/styles.css b/packages/chrome-extension/styles.css index 3a2f4c2..a1468d7 100644 --- a/packages/chrome-extension/styles.css +++ b/packages/chrome-extension/styles.css @@ -1,7 +1,7 @@ body { width: 400px; - background-color: #1e1e1e; - color: #e0e0e0; + background-color: #111827; + color: #F8FAFC; font-family: Arial, sans-serif; margin: 0; padding: 0; @@ -115,7 +115,7 @@ body { } .link { - color: #4caf50; + color: #009C59; text-decoration: none; } @@ -138,23 +138,27 @@ body { } .ai-review.low-risk { - background-color: #e6ffe6; - border: 1px solid #4CAF50; + background-color: #DCFCE7; + border: 1px solid #009C59; + color: #1F2937; } .ai-review.moderate-risk { - background-color: #ffffcc; - border: 1px solid #FFD700; + background-color: #FEFCE8; + border: 1px solid #FFB310; + color: #1F2937; } .ai-review.high-risk { - background-color: #ffe6e6; - border: 1px solid #FF0000; + background-color: #FEE2E2; + border: 1px solid #B91C1C; + color: #1F2937; } .ai-review.neutral { - background-color: #f0f0f0; - border: 1px solid #808080; + background-color: #E2E8F0; + border: 1px solid #334155; + color: #1F2937; } .hidden { diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx index bb2f06f..d2bbc86 100644 --- a/packages/nextjs/app/page.tsx +++ b/packages/nextjs/app/page.tsx @@ -1,22 +1,62 @@ "use client"; -import { useRouter } from "next/navigation"; +import { useState } from "react"; +import { useRouter, useSearchParams } from "next/navigation"; +import { Address } from "viem"; import { useAccount } from "wagmi"; +import ReviewPage from "~~/components/inspectorAiComponents/ReviewPage"; +import { AddressInput } from "~~/components/scaffold-eth"; +import { RainbowKitCustomConnectButton } from "~~/components/scaffold-eth"; + +// Expected URL structure: /?address=0x1234...5678&chain=ethereum +// Example: http://localhost:3000/?address=0x1234567890123456789012345678901234567890&chain=ethereum export default function Home() { - const router = useRouter(); const { isConnected } = useAccount(); + const router = useRouter(); + const searchParams = useSearchParams(); + + const [inputAddress, setInputAddress] = useState
(); + + console.log("page: Home component rendered, isConnected:", isConnected); + + const address = searchParams.get("address"); + const chain = searchParams.get("chain") || "ethereum"; - if (!isConnected) { - router.push("/wallet-connection"); - } else { - router.push("/world-id-verification"); - } + const handleAddressSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (inputAddress) { + router.push(`/?address=${inputAddress}&chain=${chain}`); + } + }; return (
-

Welcome to PumpInspector

-

Loading...

+ {isConnected ? ( + address ? ( + + ) : ( +
+

Review a Contract

+
+ setInputAddress(value as Address)} + placeholder="Enter contract address to review" + /> + + +
+ ) + ) : ( +
+

Welcome to Inspector AI

+

Please connect your wallet to continue.

+ +
+ )}
); } diff --git a/packages/nextjs/components/Footer.tsx b/packages/nextjs/components/Footer.tsx index 92b3c62..8b4b938 100644 --- a/packages/nextjs/components/Footer.tsx +++ b/packages/nextjs/components/Footer.tsx @@ -1,26 +1,13 @@ import React from "react"; -import Link from "next/link"; -import { hardhat } from "viem/chains"; -import { CurrencyDollarIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; -import { HeartIcon } from "@heroicons/react/24/outline"; -import { SwitchTheme } from "~~/components/SwitchTheme"; -import { BuidlGuidlLogo } from "~~/components/assets/BuidlGuidlLogo"; -import { Faucet } from "~~/components/scaffold-eth"; -import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork"; -import { useGlobalState } from "~~/services/store/store"; /** * Site footer */ export const Footer = () => { - const nativeCurrencyPrice = useGlobalState(state => state.nativeCurrency.price); - const { targetNetwork } = useTargetNetwork(); - const isLocalNetwork = targetNetwork.id === hardhat.id; - return (
-
+ {/*
{nativeCurrencyPrice > 0 && (
@@ -41,9 +28,9 @@ export const Footer = () => { )}
-
+
*/}
-
+ {/*
    @@ -74,7 +61,7 @@ export const Footer = () => {
-
+
*/}
); }; diff --git a/packages/nextjs/components/Header.tsx b/packages/nextjs/components/Header.tsx index f24a1de..0e6df28 100644 --- a/packages/nextjs/components/Header.tsx +++ b/packages/nextjs/components/Header.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useRef, useState } from "react"; import Image from "next/image"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { Bars3Icon, BugAntIcon } from "@heroicons/react/24/outline"; +import { Bars3Icon } from "@heroicons/react/24/outline"; import { FaucetButton, RainbowKitCustomConnectButton } from "~~/components/scaffold-eth"; import { useOutsideClick } from "~~/hooks/scaffold-eth"; @@ -19,11 +19,11 @@ export const menuLinks: HeaderMenuLink[] = [ label: "Home", href: "/", }, - { - label: "Debug Contracts", - href: "/debug", - icon: , - }, + // { + // label: "Debug Contracts", + // href: "/debug", + // icon: , + // }, ]; export const HeaderMenuLinks = () => { @@ -64,7 +64,7 @@ export const Header = () => { ); return ( -
+
-
- SE2 logo -
-
- Scaffold-ETH - Ethereum dev stack +
+ Inspector AI logo
    diff --git a/packages/nextjs/components/inspectorAiComponents/ReviewPage.tsx b/packages/nextjs/components/inspectorAiComponents/ReviewPage.tsx new file mode 100644 index 0000000..f12f8df --- /dev/null +++ b/packages/nextjs/components/inspectorAiComponents/ReviewPage.tsx @@ -0,0 +1,138 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { IDKitWidget, ISuccessResult, VerificationLevel } from "@worldcoin/idkit"; +import { useInspectorAI } from "~~/hooks/scaffold-eth/useInspectorAI"; + +interface ReviewPageProps { + address: string; + chain: string; +} + +export default function ReviewPage({ address, chain }: ReviewPageProps) { + console.log("ReviewPage: Rendering with props:", { address, chain }); + + const [isVerified, setIsVerified] = useState(false); + const [rating, setRating] = useState(0); + const [comment, setComment] = useState(""); + const { addReview, loadReviews, reviews, isAddingReview } = useInspectorAI(address); + + console.log("ReviewPage: Hook values:", { reviews, isAddingReview }); + + useEffect(() => { + console.log("ReviewPage: useEffect - Loading reviews"); + loadReviews(); + }, [loadReviews]); + + const handleVerification = () => { + console.log("ReviewPage: Verification successful"); + setIsVerified(true); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + console.log("ReviewPage: Submitting review", { rating, comment }); + await addReview(rating, comment); + setRating(0); + setComment(""); + }; + + const verifyProof = async (proof: ISuccessResult) => { + console.log("ReviewPage: Proof received:", proof); + handleVerification(); + }; + + console.log("ReviewPage: Current state", { isVerified, rating, comment, reviews }); + + return ( +
    +
    +
    +

    Review Contract

    +

    Chain: {chain}

    +

    Address: {address}

    +
    +
    +
    + {[1, 2, 3, 4, 5].map(star => ( + setRating(star)} + /> + ))} +
    +
    +
    + +
    +
    + + {({ open }) => ( + + )} + +
    +
    + +
    +
    +
    +
    + +
    +

    Reviews

    + {reviews.length > 0 ? ( +
      + {reviews.map((review, index) => ( +
    • +
      +
      + {[1, 2, 3, 4, 5].map(star => ( + + ))} +
      + by {review.reviewer} +
      +

      {review.comment}

      +
    • + ))} +
    + ) : ( +

    No reviews yet.

    + )} +
    +
    + ); +} diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index 13d75b4..594e280 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -7,7 +7,7 @@ import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; const deployedContracts = { 31337: { InspectorAI: { - address: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0", + address: "0x5FbDB2315678afecb367f032d93F642f64180aa3", abi: [ { anonymous: false, diff --git a/packages/nextjs/hooks/scaffold-eth/useInspectorAI.ts b/packages/nextjs/hooks/scaffold-eth/useInspectorAI.ts new file mode 100644 index 0000000..fa70ea1 --- /dev/null +++ b/packages/nextjs/hooks/scaffold-eth/useInspectorAI.ts @@ -0,0 +1,68 @@ +import { useCallback, useState } from "react"; +import { useScaffoldReadContract } from "./useScaffoldReadContract"; +import { useScaffoldWriteContract } from "./useScaffoldWriteContract"; +import confetti from "canvas-confetti"; +import { notification } from "~~/utils/scaffold-eth"; + +export const useInspectorAI = (contractAddress: string) => { + const [reviews, setReviews] = useState([]); + + console.log("[useInspectorAI] Initializing hook with contractAddress:", contractAddress); + + const { data: fetchedReviews, refetch: refetchReviews } = useScaffoldReadContract({ + contractName: "InspectorAI", + functionName: "getReviews", + args: [contractAddress], + }); + + console.log("[useInspectorAI] fetchedReviews:", fetchedReviews); + + const { writeContract: addReviewAsync, isMining: isAddingReview } = useScaffoldWriteContract("InspectorAI"); + + const addReview = useCallback( + async (rating: number, comment: string) => { + console.log("[useInspectorAI] addReview called with rating:", rating, "comment:", comment); + try { + console.log("[useInspectorAI] Calling addReview function"); + await addReviewAsync({ + functionName: "addReview", + args: [contractAddress, BigInt(rating) as any, comment], + }); + + console.log("[useInspectorAI] Transaction sent"); + + // The transaction is already confirmed by the time we reach here + console.log("[useInspectorAI] Transaction confirmed"); + + notification.success("Review added successfully!"); + confetti({ + particleCount: 100, + spread: 70, + origin: { y: 0.6 }, + }); + await refetchReviews(); + } catch (error) { + console.error("[useInspectorAI] Error adding review:", error); + notification.error("Failed to add review"); + } + }, + [addReviewAsync, contractAddress, refetchReviews], + ); + + const loadReviews = useCallback(async () => { + console.log("[useInspectorAI] loadReviews called"); + try { + await refetchReviews(); + if (fetchedReviews) { + console.log("[useInspectorAI] Fetched reviews:", fetchedReviews); + setReviews(fetchedReviews as any[]); + } + } catch (error) { + console.error("[useInspectorAI] Error loading reviews:", error); + notification.error("Failed to load reviews"); + } + }, [refetchReviews, fetchedReviews]); + + console.log("[useInspectorAI] Returning hook functions"); + return { addReview, loadReviews, reviews, isAddingReview }; +}; diff --git a/packages/nextjs/public/logo_with_name.png b/packages/nextjs/public/logo_with_name.png new file mode 100644 index 0000000000000000000000000000000000000000..e05ba4ecd00a114f773a82e3e7887b5bb88ba078 GIT binary patch literal 8571 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 UTMgj%N&sHJ(tKI