diff --git a/.env.local.example b/.env.local.example index 5a2f7b8..aa5cb6e 100644 --- a/.env.local.example +++ b/.env.local.example @@ -11,6 +11,7 @@ NEXT_PUBLIC_HATHOR_NODE_URL_TESTNET=https://node1.india-testnet.hathor.network/v NEXT_PUBLIC_HATHOR_NODE_URL_MAINNET=https://node1.mainnet.hathor.network/v1a # Contract IDs -# Add your deployed contract IDs as a JSON array +# Add deployed contract IDs as JSON arrays per network # Example: ["0abc123...", "def456..."] -NEXT_PUBLIC_CONTRACT_IDS=[] +NEXT_PUBLIC_CONTRACT_IDS_TESTNET=[] +NEXT_PUBLIC_CONTRACT_IDS_MAINNET=[] diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index bffb357..0000000 --- a/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/.github/workflows/pr-preview-cleanup.yml b/.github/workflows/pr-preview-cleanup.yml new file mode 100644 index 0000000..9cfbb56 --- /dev/null +++ b/.github/workflows/pr-preview-cleanup.yml @@ -0,0 +1,69 @@ +name: PR Preview Cleanup + +on: + pull_request: + types: [closed] + branches: + - master + - main + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + cleanup-preview: + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - name: Remove PR preview directory from gh-pages + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + REPO_URL="https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + + if ! git ls-remote --exit-code --heads "$REPO_URL" gh-pages >/dev/null; then + echo "No gh-pages branch found, nothing to clean up." + exit 0 + fi + + git clone --single-branch --branch gh-pages --depth 1 "$REPO_URL" gh-pages-out + + PR_DIR="gh-pages-out/pr-${PR_NUMBER}" + if [ ! -d "$PR_DIR" ]; then + echo "No preview directory found for PR #${PR_NUMBER}, nothing to clean up." + exit 0 + fi + + cd gh-pages-out + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git rm -rf "pr-${PR_NUMBER}" + git commit -m "chore: remove preview for PR #${PR_NUMBER}" + git push origin gh-pages + + - name: Update PR comment + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + export GH_TOKEN="$GITHUB_TOKEN" + MARKER="" + + COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments?per_page=100" \ + --jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" | head -1) + + if [ -z "$COMMENT_ID" ]; then + echo "No preview comment found, nothing to update." + exit 0 + fi + + BODY="${MARKER} + ## Preview deployment + + Preview has been removed as this PR is now closed." + + gh api -X PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" \ + --input - <<< "$(jq -n --arg body "$BODY" '{"body": $body}')" \ No newline at end of file diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml new file mode 100644 index 0000000..8e64b14 --- /dev/null +++ b/.github/workflows/pr-preview.yml @@ -0,0 +1,115 @@ +name: PR Preview + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: + - master + - main + +concurrency: + group: pr-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + deploy-preview: + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - name: Checkout repository + # https://github.com/actions/checkout/releases/tag/v4.2.2 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + - name: Set up Node.js 22.x + # https://github.com/actions/setup-node/releases/tag/v6.1.0 + uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f + with: + node-version: 22.x + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build static preview + env: + NEXT_OUTPUT_MODE: export + NEXT_PUBLIC_BASE_PATH: /${{ github.event.repository.name }}/pr-${{ github.event.pull_request.number }} + NEXT_PUBLIC_DEFAULT_NETWORK: testnet + NEXT_PUBLIC_HATHOR_NODE_URL_TESTNET: https://node1.india.testnet.hathor.network/v1a + NEXT_PUBLIC_HATHOR_NODE_URL_MAINNET: https://node1.mainnet.hathor.network/v1a + NEXT_PUBLIC_CONTRACT_IDS_TESTNET: '["00000000361ec0406d90a5bb4c6c7330af5792178b86cfc353afd4e50a62b741"]' + NEXT_PUBLIC_CONTRACT_IDS_MAINNET: '["000003e0baf17eee5a25aa0ccf36eb331a05818c87bc1c316f54485aa974c485"]' + NEXT_PUBLIC_USE_MOCK_WALLET: 'false' + NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID: 8264fff563181da658ce64ee80e80458 + run: npm run build + + - name: Deploy to gh-pages branch + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + REPO_URL="https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + + if git clone --single-branch --branch gh-pages --depth 1 "$REPO_URL" gh-pages-out 2>/dev/null; then + echo "Cloned existing gh-pages branch" + else + echo "Creating new gh-pages branch" + mkdir gh-pages-out + cd gh-pages-out + git init + git remote add origin "$REPO_URL" + git checkout --orphan gh-pages + cd .. + fi + + touch gh-pages-out/.nojekyll + rm -rf "gh-pages-out/pr-${PR_NUMBER}" + mkdir -p "gh-pages-out/pr-${PR_NUMBER}" + cp -r out/. "gh-pages-out/pr-${PR_NUMBER}/" + + cd gh-pages-out + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add .nojekyll "pr-${PR_NUMBER}/" + git diff --cached --quiet || git commit -m "deploy: preview for PR #${PR_NUMBER} @ ${HEAD_SHA}" + git push origin gh-pages + + - name: Post or update PR comment + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + export GH_TOKEN="$GITHUB_TOKEN" + MARKER="" + REPO_NAME="${GITHUB_REPOSITORY#*/}" + PREVIEW_URL="https://${GITHUB_REPOSITORY_OWNER}.github.io/${REPO_NAME}/pr-${PR_NUMBER}/" + + BODY="${MARKER} + ## Preview deployment + + | | | + |---|---| + | **URL** | ${PREVIEW_URL} | + | **Commit** | \`${HEAD_SHA}\` | + + > This preview is automatically updated on every push and removed when the PR is closed." + + COMMENT_ID=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments?per_page=100" \ + --jq ".[] | select(.body | startswith(\"${MARKER}\")) | .id" | head -1) + + PAYLOAD=$(jq -n --arg body "$BODY" '{"body": $body}') + + if [ -n "$COMMENT_ID" ]; then + gh api -X PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${COMMENT_ID}" \ + --input - <<< "$PAYLOAD" + else + gh api -X POST "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" \ + --input - <<< "$PAYLOAD" + fi \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 644a83e..a3fd8dd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Frontend Dockerfile - Multi-stage build for Next.js SSR -FROM node:18-alpine AS builder +FROM node:22-alpine AS builder # Set working directory WORKDIR /app @@ -22,7 +22,8 @@ ENV NEXT_PUBLIC_USE_MOCK_WALLET=false ENV NEXT_PUBLIC_DEFAULT_NETWORK=testnet ENV NEXT_PUBLIC_HATHOR_NODE_URL_TESTNET=https://node1.india.testnet.hathor.network/v1a ENV NEXT_PUBLIC_HATHOR_NODE_URL_MAINNET=https://node1.mainnet.hathor.network/v1a -ENV NEXT_PUBLIC_CONTRACT_IDS='["0000000079862340c1f7822b81f58668e2a62c5f1b69d8d2e3b8fdf1855196c1"]' +ENV NEXT_PUBLIC_CONTRACT_IDS_TESTNET='["00000000361ec0406d90a5bb4c6c7330af5792178b86cfc353afd4e50a62b741"]' +ENV NEXT_PUBLIC_CONTRACT_IDS_MAINNET='["000003e0baf17eee5a25aa0ccf36eb331a05818c87bc1c316f54485aa974c485"]' ENV NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=8264fff563181da658ce64ee80e80458 # Set environment for production build @@ -32,7 +33,7 @@ ENV NEXT_TELEMETRY_DISABLED=1 RUN npm run build # Production stage -FROM node:18-alpine AS runner +FROM node:22-alpine AS runner # Install curl for health checks and other utilities RUN apk add --no-cache curl diff --git a/INTEGRATION.md b/INTEGRATION.md index a6fe50f..4e3a90b 100644 --- a/INTEGRATION.md +++ b/INTEGRATION.md @@ -174,7 +174,8 @@ NEXT_PUBLIC_USE_MOCK_WALLET=true NEXT_PUBLIC_DEFAULT_NETWORK=testnet NEXT_PUBLIC_HATHOR_NODE_URL_TESTNET=https://node1.india.testnet.hathor.network/v1a NEXT_PUBLIC_HATHOR_NODE_URL_MAINNET=https://node1.mainnet.hathor.network/v1a -NEXT_PUBLIC_CONTRACT_IDS=["contract_id"] +NEXT_PUBLIC_CONTRACT_IDS_TESTNET=["contract_id"] +NEXT_PUBLIC_CONTRACT_IDS_MAINNET=[] ``` ## Testing @@ -194,7 +195,7 @@ NEXT_PUBLIC_CONTRACT_IDS=["contract_id"] ### Immediate 1. Deploy contract to India Testnet -2. Update `NEXT_PUBLIC_CONTRACT_IDS` with real contract ID +2. Update `NEXT_PUBLIC_CONTRACT_IDS_TESTNET` with real contract ID 3. Test with real wallet 4. Verify all calculations match contract diff --git a/QUICKSTART.md b/QUICKSTART.md index cf85a30..f871cfd 100644 --- a/QUICKSTART.md +++ b/QUICKSTART.md @@ -45,7 +45,8 @@ All actions are simulated and no real transactions occur. 1. **Update Environment**: ```env NEXT_PUBLIC_USE_MOCK_WALLET=false -NEXT_PUBLIC_CONTRACT_IDS=["your_contract_id_here"] +NEXT_PUBLIC_CONTRACT_IDS_TESTNET=["your_contract_id_here"] +NEXT_PUBLIC_CONTRACT_IDS_MAINNET=[] ``` 2. **Restart Server**: @@ -89,7 +90,7 @@ The Contract Information panel shows: - Check that you're on the correct network ### "Contract state not loaded" -- Verify `NEXT_PUBLIC_CONTRACT_IDS` is set correctly +- Verify `NEXT_PUBLIC_CONTRACT_IDS_TESTNET` or `NEXT_PUBLIC_CONTRACT_IDS_MAINNET` is set correctly for the selected network - Check that the contract exists on the selected network - Ensure node URL is accessible diff --git a/README.md b/README.md index c36b6f8..28d4962 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,8 @@ NEXT_PUBLIC_USE_MOCK_WALLET=true NEXT_PUBLIC_DEFAULT_NETWORK=india-testnet NEXT_PUBLIC_HATHOR_NODE_URL_TESTNET=https://node1.india-testnet.hathor.network/v1a NEXT_PUBLIC_HATHOR_NODE_URL_MAINNET=https://node1.mainnet.hathor.network/v1a -NEXT_PUBLIC_CONTRACT_IDS=["contract_id_1","contract_id_2"] +NEXT_PUBLIC_CONTRACT_IDS_TESTNET=["contract_id_1","contract_id_2"] +NEXT_PUBLIC_CONTRACT_IDS_MAINNET=[] ``` ## Setup Instructions @@ -86,7 +87,7 @@ cp .env.local.example .env.local Edit `.env.local` with your configuration: - Set `NEXT_PUBLIC_USE_MOCK_WALLET=false` for production -- Add your contract IDs to `NEXT_PUBLIC_CONTRACT_IDS` +- Add your contract IDs to `NEXT_PUBLIC_CONTRACT_IDS_TESTNET` and/or `NEXT_PUBLIC_CONTRACT_IDS_MAINNET` - Configure network URLs if using custom nodes 4. Run development server: diff --git a/STATUS.md b/STATUS.md index 7780591..cb0d003 100644 --- a/STATUS.md +++ b/STATUS.md @@ -102,13 +102,15 @@ NEXT_PUBLIC_USE_MOCK_WALLET=true NEXT_PUBLIC_DEFAULT_NETWORK=india-testnet NEXT_PUBLIC_HATHOR_NODE_URL_TESTNET=https://node1.india.testnet.hathor.network/v1a NEXT_PUBLIC_HATHOR_NODE_URL_MAINNET=https://node1.mainnet.hathor.network/v1a -NEXT_PUBLIC_CONTRACT_IDS=[] +NEXT_PUBLIC_CONTRACT_IDS_TESTNET=[] +NEXT_PUBLIC_CONTRACT_IDS_MAINNET=[] ``` ### For Production ```env NEXT_PUBLIC_USE_MOCK_WALLET=false -NEXT_PUBLIC_CONTRACT_IDS=["your_deployed_contract_id"] +NEXT_PUBLIC_CONTRACT_IDS_TESTNET=[] +NEXT_PUBLIC_CONTRACT_IDS_MAINNET=["your_deployed_contract_id"] ``` ## Testing Checklist diff --git a/app/layout.tsx b/app/layout.tsx index 56a8c22..318580d 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,6 +4,7 @@ import { HathorProvider } from '@/contexts/HathorContext'; import { WalletConnectProvider } from '@/contexts/WalletConnectContext'; import { MetaMaskProvider } from '@/contexts/MetaMaskContext'; import { UnifiedWalletProvider } from '@/contexts/UnifiedWalletContext'; +import { publicAsset } from '@/lib/publicAsset'; import { ToastProvider, Toaster } from '@/lib/toast'; import './globals.css'; @@ -11,7 +12,7 @@ export const metadata: Metadata = { title: 'Hathor Dice - Provably Fair Betting', description: 'Decentralized dice game on Hathor Network', icons: { - icon: '/images/icon.png', + icon: publicAsset('/images/icon.png'), }, }; diff --git a/components/GoddessSpinner.tsx b/components/GoddessSpinner.tsx index 7c14a01..f139897 100644 --- a/components/GoddessSpinner.tsx +++ b/components/GoddessSpinner.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; +import { publicAsset } from '@/lib/publicAsset'; interface GoddessSpinnerProps { size?: number; @@ -7,12 +8,12 @@ interface GoddessSpinnerProps { } const GODDESS_IMAGES = [ - '/images/cartoon_anubis.png', - '/images/cartoon_pharaoh.png', - '/images/cartoon_scarab.png', - '/images/cartoon_eye_of_horus.png', - '/images/cartoon_pyramid.png', - '/images/cartoon_mummy.png', + publicAsset('/images/cartoon_anubis.png'), + publicAsset('/images/cartoon_pharaoh.png'), + publicAsset('/images/cartoon_scarab.png'), + publicAsset('/images/cartoon_eye_of_horus.png'), + publicAsset('/images/cartoon_pyramid.png'), + publicAsset('/images/cartoon_mummy.png'), ]; export function GoddessSpinner({ size = 64, interval = 800 }: GoddessSpinnerProps) { diff --git a/components/Header.tsx b/components/Header.tsx index a0f8e6e..a31b5b1 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { useHathor } from '@/contexts/HathorContext'; +import { publicAsset } from '@/lib/publicAsset'; import { formatAddress } from '@/lib/utils'; import { WalletConnectionModal } from './WalletConnectionModal'; import { NetworkSelector } from './NetworkSelector'; @@ -16,6 +17,7 @@ export default function Header({ selectedToken, onTokenChange }: HeaderProps) { const { isConnected, address, disconnectWallet, network, switchNetwork } = useHathor(); const [showModal, setShowModal] = useState(false); const [showDisconnectMenu, setShowDisconnectMenu] = useState(false); + const iconSrc = publicAsset('/images/icon.png'); const handleConnect = async () => { setShowModal(true); @@ -34,7 +36,7 @@ export default function Header({ selectedToken, onTokenChange }: HeaderProps) {

HATHOR DICE

- Hathor Dice + Hathor Dice
diff --git a/components/IntroVideo.tsx b/components/IntroVideo.tsx index 29c1faf..6dca522 100644 --- a/components/IntroVideo.tsx +++ b/components/IntroVideo.tsx @@ -1,6 +1,7 @@ 'use client'; import { useState, useRef, useEffect } from 'react'; +import { publicAsset } from '@/lib/publicAsset'; interface IntroVideoProps { onComplete: () => void; @@ -10,6 +11,8 @@ export function IntroVideo({ onComplete }: IntroVideoProps) { const videoRef = useRef(null); const [showPlayButton, setShowPlayButton] = useState(true); const onCompleteRef = useRef(onComplete); + const videoSrc = publicAsset('/videos/intro.mp4'); + const posterSrc = publicAsset('/images/intro-poster.png'); // Keep ref updated useEffect(() => { @@ -50,8 +53,8 @@ export function IntroVideo({ onComplete }: IntroVideoProps) { >