diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index e4a8956..589b4c0 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -1,22 +1,22 @@
-name: CI
+name: Run Tests
on:
push:
- branches: [main, master]
+ branches: [main, develop]
pull_request:
- branches: [main, master]
+ branches: [main, develop]
jobs:
test:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v3
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v3
with:
- node-version: '20'
+ node-version: '20.x'
- name: Setup pnpm
uses: pnpm/action-setup@v2
@@ -39,11 +39,9 @@ jobs:
- name: Install dependencies
run: pnpm install
- - name: Run Prettier check
- run: pnpm format:check
-
- name: Run tests
run: pnpm test
-
- - name: Run ESLint
- run: pnpm lint
+ env:
+ CI: true
+ NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID }}
+ NEXT_PUBLIC_RPC_ENDPOINT_URL: ${{ secrets.NEXT_PUBLIC_RPC_ENDPOINT_URL }}
diff --git a/.gitignore b/.gitignore
index 826da96..d355055 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,4 +39,6 @@ next-env.d.ts
# Misc
-NOTES.md
\ No newline at end of file
+NOTES.md
+genji_app_description*
+.swc
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index d7df89c..0dbcbdd 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,3 +1,10 @@
+
{
- "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
-}
+ "recommendations": [
+ "dbaeumer.vscode-eslint",
+ "esbenp.prettier-vscode",
+ "bradlc.vscode-tailwindcss",
+ "csstools.postcss",
+ "ms-vscode.vscode-typescript-next"
+ ]
+ }
diff --git a/.vscode/settings.json b/.vscode/settings.json
index c5a02a6..d66a4ea 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,25 +1,24 @@
{
- "editor.defaultFormatter": "esbenp.prettier-vscode",
- "editor.formatOnSave": true,
- "editor.codeActionsOnSave": {
- "source.fixAll.eslint": true
- },
- "[typescript]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- },
- "[typescriptreact]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- },
- "[javascript]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- },
- "[javascriptreact]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- },
- "[json]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- },
- "[markdown]": {
- "editor.defaultFormatter": "esbenp.prettier-vscode"
- }
-}
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ },
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "typescript.enablePromptUseWorkspaceTsdk": true,
+ "eslint.validate": [
+ "javascript",
+ "javascriptreact",
+ "typescript",
+ "typescriptreact"
+ ],
+ "files.associations": {
+ "*.css": "tailwindcss"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ },
+ "[typescriptreact]": {
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
+ }
+ }
\ No newline at end of file
diff --git a/QUICKSTART.md b/QUICKSTART.md
new file mode 100644
index 0000000..deab628
--- /dev/null
+++ b/QUICKSTART.md
@@ -0,0 +1,230 @@
+# Genji - Quick Start Guide
+
+A modern Web3 application template featuring Next.js, Reown, Ethers.js, and Chakra UI.
+
+🔥 [Live Demo](https://genji-app.netlify.app)
+
+## Core Technologies
+
+- 🚀 [Next.js](https://nextjs.org/) - React framework for production
+- 🔗 [Reown](https://reown.com/appkit) - Web3 authentication & wallet connections
+- âš¡ [Ethers.js](https://ethers.org/) (v6) - Ethereum library
+- 💅 [Chakra UI](https://chakra-ui.com/) - Component library
+- 🔧 [Example Smart Contract](https://github.com/w3hc/w3hc-hardhat-template/blob/main/contracts/Basic.sol)
+
+## Features
+
+- Multi-wallet support
+- Email & social logins (Google, Farcaster, GitHub)
+- Multiple network support (Sepolia, Optimism, zkSync, Base, etc.)
+- Dark/Light theme
+- Built-in faucet API
+- TypeScript
+- Testing setup with Jest
+- ESLint + Prettier config
+
+## Prerequisites
+
+```bash
+node -v # v20.9.0 or later
+pnpm -v # v8.7.5 or later
+```
+
+## Installation
+
+1. Clone the repository:
+
+```bash
+git clone https://github.com/your-username/genji.git
+cd genji
+```
+
+2. Install dependencies:
+
+```bash
+pnpm install
+```
+
+3. Set up environment:
+
+```bash
+cp .env.example .env
+```
+
+4. Configure `.env`:
+
+```
+# Get yours at https://cloud.walletconnect.com
+NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID='your_project_id'
+
+# RPC endpoint
+NEXT_PUBLIC_RPC_ENDPOINT_URL='https://sepolia.gateway.tenderly.co'
+
+# Only needed if using the faucet API
+NEXT_PUBLIC_SIGNER_PRIVATE_KEY='your_private_key'
+```
+
+5. Start development server:
+
+```bash
+pnpm dev
+```
+
+Visit `http://localhost:3000` 🚀
+
+## Project Structure
+
+```
+src/
+├── components/ # UI components
+│ ├── Header.tsx # Main navigation
+│ ├── layout/ # Layout components
+│ └── ...
+├── context/
+│ └── web3modal.tsx # Web3 configuration
+├── hooks/ # Custom React hooks
+├── pages/ # Next.js pages
+│ ├── api/ # API routes
+│ │ └── faucet.ts
+│ └── index.tsx
+└── utils/ # Helpers & constants
+ ├── config.ts
+ └── erc20.ts
+```
+
+## Usage Examples
+
+### 1. Connect Wallet
+
+```typescript
+import { useAppKitAccount, useAppKitProvider } from '@reown/appkit/react'
+
+export default function YourComponent() {
+ const { address, isConnected } = useAppKitAccount()
+ const { walletProvider } = useAppKitProvider('eip155')
+
+ if (!isConnected) {
+ return // Reown connect button
+ }
+
+ return
Connected: {address}
+}
+```
+
+### 2. Contract Interaction
+
+```typescript
+// Initialize contract
+const ethersProvider = new BrowserProvider(walletProvider as Eip1193Provider)
+const signer = await ethersProvider.getSigner()
+const contract = new Contract(ERC20_CONTRACT_ADDRESS, ERC20_CONTRACT_ABI, signer)
+
+// Call contract method
+const tx = await contract.mint(parseEther('1000'))
+const receipt = await tx.wait()
+```
+
+### 3. UI Components
+
+```typescript
+import { Button, useToast } from '@chakra-ui/react'
+
+export default function YourComponent() {
+ const toast = useToast()
+
+ const handleClick = () => {
+ toast({
+ title: 'Success!',
+ status: 'success',
+ duration: 9000,
+ isClosable: true,
+ })
+ }
+
+ return (
+
+ )
+}
+```
+
+## Testing
+
+Run tests:
+
+```bash
+pnpm test # Run all tests
+pnpm test:watch # Watch mode
+```
+
+## Network Support
+
+The template supports multiple networks. Configure in `src/context/web3modal.tsx`:
+
+```typescript
+const networks = [
+ sepolia, // Default network
+ optimism,
+ zksync,
+ base,
+ arbitrum,
+ gnosis,
+ polygon,
+ polygonZkEvm,
+ mantle,
+ celo,
+ avalanche,
+ degen,
+]
+```
+
+## Browser Support
+
+- iOS: Safari 10+ (iOS 10+)
+- Android: Chrome 51+ (Android 5.0+)
+- Desktop: Modern browsers
+
+## Customization
+
+### Theme
+
+Modify in `src/utils/config.ts`:
+
+```typescript
+export const THEME_COLOR_SCHEME = 'blue'
+export const THEME_INITIAL_COLOR = 'system'
+```
+
+### Contract Setup
+
+1. Update contract details in `src/utils/erc20.ts`
+2. Implement your interaction logic
+
+## Development Commands
+
+```bash
+pnpm dev # Start development server
+pnpm build # Production build
+pnpm start # Start production server
+pnpm lint # Run ESLint
+pnpm format # Format code with Prettier
+```
+
+## Support & Resources
+
+- 📘 [Next.js Documentation](https://nextjs.org/docs)
+- 🔧 [Reown AppKit Guide](https://reown.com/appkit)
+- âš¡ [Ethers.js Documentation](https://docs.ethers.org/v6/)
+- 💅 [Chakra UI Components](https://chakra-ui.com/docs/components)
+
+## Contact
+
+Need help? Reach out:
+
+- [Element](https://matrix.to/#/@julienbrg:matrix.org)
+- [Farcaster](https://warpcast.com/julien-)
+- [Telegram](https://t.me/julienbrg)
+- [Twitter](https://twitter.com/julienbrg)
+- [Discord](https://discordapp.com/users/julienbrg)
+- [LinkedIn](https://www.linkedin.com/in/julienberanger/)
diff --git a/README.md b/README.md
index 70865ed..0a07b24 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
A Next.js Web3 app template.
+You can read **[our quickstart](https://github.com/w3hc/genji/blob/main/README.md)** to get started!
+
## Features
- [Next.js](https://nextjs.org/)
@@ -27,24 +29,12 @@ Create a `.env` file:
cp .env.example .env
```
-Add your own keys in the `.env` file (you can get it in your [Wallet Connect dashboard](https://cloud.walletconnect.com)), then:
+Add your own keys in the `.env` file (you can get it in your [Reown dashboard](https://cloud.reown.com/)), then:
```bash
pnpm dev
```
-## Requirements
-
-Here are the known minimal mobile hardware requirements:
-
-- iOS: Safari 10+ (iOS 10+)
-- Android: Chrome 51+ (Android 5.0+)
-
-## Versions
-
-- pnpm `v8.7.5`
-- node `v20.9.0`
-
## Support
You can contact me via [Element](https://matrix.to/#/@julienbrg:matrix.org), [Farcaster](https://warpcast.com/julien-), [Telegram](https://t.me/julienbrg), [Twitter](https://twitter.com/julienbrg), [Discord](https://discordapp.com/users/julienbrg), or [LinkedIn](https://www.linkedin.com/in/julienberanger/).
diff --git a/genji_app_description.md b/genji_app_description.md
deleted file mode 100644
index 113059e..0000000
--- a/genji_app_description.md
+++ /dev/null
@@ -1,1916 +0,0 @@
-# genji
-
-
-### .env.example
-
-```
-NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID='88888' # Get yours at https://cloud.walletconnect.com
-NEXT_PUBLIC_RPC_ENDPOINT_URL='https://sepolia.gateway.tenderly.co'
-NEXT_PUBLIC_SIGNER_PRIVATE_KEY='88888'
-```
-
-### .eslintignore
-
-```
-# Ignore everything
-# *
-```
-
-### .eslintrc.json
-
-```json
-{
- "extends": ["next/core-web-vitals", "prettier"],
- "plugins": ["prettier"],
- "rules": {
- "prettier/prettier": "error",
- "arrow-body-style": "off",
- "prefer-arrow-callback": "off"
- }
-}
-
-```
-
-### .gitignore
-
-```
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-# local env files
-.env*.local
-.env
-
-# vercel
-.vercel
-
-# typescript
-
-*.tsbuildinfo
-next-env.d.ts
-/.history
-
-# Misc
-
-NOTES.md
-```
-
-### .prettierignore
-
-```
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-genji_app_description*
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-# local env files
-.env*.local
-.env
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
-
-# IDE
-.idea
-.vscode
-
-# package manager
-pnpm-lock.yaml
-package-lock.json
-yarn.lock
-```
-
-### .prettierrc.json
-
-```json
-{
- "trailingComma": "es5",
- "semi": false,
- "singleQuote": true,
- "printWidth": 120,
- "bracketSameLine": true,
- "useTabs": false,
- "tabWidth": 2,
- "bracketSpacing": true,
- "arrowParens": "always",
- "endOfLine": "lf",
- "htmlWhitespaceSensitivity": "css",
- "insertPragma": false,
- "jsxSingleQuote": false,
- "proseWrap": "preserve",
- "quoteProps": "as-needed",
- "requirePragma": false
-}
-
-```
-
-### README.md
-
-```markdown
-# Genji
-
-A Next.js Web3 app template.
-
-## Features
-
-- [Next.js](https://nextjs.org/)
-- [Reown](https://reown.com/appkit)
-- [Ethers.js](https://ethers.org/) (v6)
-- [Chakra UI](https://chakra-ui.com/)
-
-View the [Solidity contract](https://github.com/w3hc/w3hc-hardhat-template/blob/main/contracts/Basic.sol) used in the example.
-
-Web app live at [https://genji-app.netlify.app](https://genji-app.netlify.app).
-
-## Install
-
-```bash
-pnpm i
-```
-
-## Run
-
-Create a `.env` file:
-
-```
-cp .env.example .env
-```
-
-Add your own keys in the `.env` file (you can get it in your [Wallet Connect dashboard](https://cloud.walletconnect.com)), then:
-
-```bash
-pnpm dev
-```
-
-## Requirements
-
-Here are the known minimal mobile hardware requirements:
-
-- iOS: Safari 10+ (iOS 10+)
-- Android: Chrome 51+ (Android 5.0+)
-
-## Versions
-
-- pnpm `v8.7.5`
-- node `v20.9.0`
-
-## Support
-
-You can contact me via [Element](https://matrix.to/#/@julienbrg:matrix.org), [Farcaster](https://warpcast.com/julien-), [Telegram](https://t.me/julienbrg), [Twitter](https://twitter.com/julienbrg), [Discord](https://discordapp.com/users/julienbrg), or [LinkedIn](https://www.linkedin.com/in/julienberanger/).
-
-## Credits
-
-Special thanks to Wesley ([@wslyvh](https://github.com/wslyvh)) for building [Nexth](https://github.com/wslyvh/nexth). I also want to thank the [Wallet Connect](https://walletconnect.com/) team, [@glitch-txs](https://github.com/glitch-txs) in particular. And of course [@ricmoo](https://github.com/ricmoo) for maintaining [Ethers.js](https://ethers.org/)!
-
-```
-
-### genji_app_description.md
-
-```markdown
-# genji
-
-
-### .env.example
-
-```
-NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID='88888' # Get yours at https://cloud.walletconnect.com
-NEXT_PUBLIC_RPC_ENDPOINT_URL='https://sepolia.gateway.tenderly.co'
-NEXT_PUBLIC_SIGNER_PRIVATE_KEY='88888'
-```
-
-### .eslintignore
-
-```
-# Ignore everything
-# *
-```
-
-### .eslintrc.json
-
-```json
-{
- "extends": ["next/core-web-vitals", "prettier"],
- "plugins": ["prettier"],
- "rules": {
- "prettier/prettier": "error",
- "arrow-body-style": "off",
- "prefer-arrow-callback": "off"
- }
-}
-
-```
-
-### .gitignore
-
-```
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-# local env files
-.env*.local
-.env
-
-# vercel
-.vercel
-
-# typescript
-
-*.tsbuildinfo
-next-env.d.ts
-/.history
-
-# Misc
-
-NOTES.md
-```
-
-### .prettierignore
-
-```
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-genji_app_description*
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-
-# local env files
-.env*.local
-.env
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo
-next-env.d.ts
-
-# IDE
-.idea
-.vscode
-
-# package manager
-pnpm-lock.yaml
-package-lock.json
-yarn.lock
-```
-
-### .prettierrc.json
-
-```json
-{
- "trailingComma": "es5",
- "semi": false,
- "singleQuote": true,
- "printWidth": 120,
- "bracketSameLine": true,
- "useTabs": false,
- "tabWidth": 2,
- "bracketSpacing": true,
- "arrowParens": "always",
- "endOfLine": "lf",
- "htmlWhitespaceSensitivity": "css",
- "insertPragma": false,
- "jsxSingleQuote": false,
- "proseWrap": "preserve",
- "quoteProps": "as-needed",
- "requirePragma": false
-}
-
-```
-
-### README.md
-
-```markdown
-# Genji
-
-A Next.js Web3 app template.
-
-## Features
-
-- [Next.js](https://nextjs.org/)
-- [Reown](https://reown.com/appkit)
-- [Ethers.js](https://ethers.org/) (v6)
-- [Chakra UI](https://chakra-ui.com/)
-
-View the [Solidity contract](https://github.com/w3hc/w3hc-hardhat-template/blob/main/contracts/Basic.sol) used in the example.
-
-Web app live at [https://genji-app.netlify.app](https://genji-app.netlify.app).
-
-## Install
-
-```bash
-pnpm i
-```
-
-## Run
-
-Create a `.env` file:
-
-```
-cp .env.example .env
-```
-
-Add your own keys in the `.env` file (you can get it in your [Wallet Connect dashboard](https://cloud.walletconnect.com)), then:
-
-```bash
-pnpm dev
-```
-
-## Requirements
-
-Here are the known minimal mobile hardware requirements:
-
-- iOS: Safari 10+ (iOS 10+)
-- Android: Chrome 51+ (Android 5.0+)
-
-## Versions
-
-- pnpm `v8.7.5`
-- node `v20.9.0`
-
-## Support
-
-You can contact me via [Element](https://matrix.to/#/@julienbrg:matrix.org), [Farcaster](https://warpcast.com/julien-), [Telegram](https://t.me/julienbrg), [Twitter](https://twitter.com/julienbrg), [Discord](https://discordapp.com/users/julienbrg), or [LinkedIn](https://www.linkedin.com/in/julienberanger/).
-
-## Credits
-
-Special thanks to Wesley ([@wslyvh](https://github.com/wslyvh)) for building [Nexth](https://github.com/wslyvh/nexth). I also want to thank the [Wallet Connect](https://walletconnect.com/) team, [@glitch-txs](https://github.com/glitch-txs) in particular. And of course [@ricmoo](https://github.com/ricmoo) for maintaining [Ethers.js](https://ethers.org/)!
-
-```
-
-### genji_app_description.md
-
-```markdown
-
-```
-
-### jest.config.ts
-
-```typescript
-import type { Config } from 'jest'
-import nextJest from 'next/jest'
-
-const createJestConfig = nextJest({
- dir: './',
-})
-
-const customJestConfig: Config = {
- preset: 'ts-jest',
- setupFilesAfterEnv: ['/jest.setup.ts'],
- testEnvironment: 'jest-environment-jsdom',
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
- transform: {
- '^.+\\.(ts|tsx)$': 'ts-jest',
- },
-}
-
-export default createJestConfig(customJestConfig)
-
-```
-
-### jest.setup.ts
-
-```typescript
-import '@testing-library/jest-dom'
-
-```
-
-### next.config.js
-
-```javascript
-/** @type {import('next').NextConfig} */
-const nextConfig = {
- reactStrictMode: true,
- eslint: {
- ignoreDuringBuilds: true,
- },
-}
-
-module.exports = nextConfig
-
-```
-
-### package.json
-
-```json
-{
- "name": "genji",
- "description": "A Next.js Web3 app template",
- "version": "0.1.0",
- "scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
- "lint": "next lint",
- "test": "jest",
- "test:watch": "jest --watch",
- "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
- "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\""
- },
- "dependencies": {
- "@chakra-ui/icons": "^2.1.1",
- "@chakra-ui/next-js": "^2.2.0",
- "@chakra-ui/react": "^2.8.2",
- "@coinbase/wallet-sdk": "4.0.3",
- "@emotion/react": "^11.13.3",
- "@emotion/styled": "^11.13.0",
- "@reown/appkit": "^1.1.2",
- "@reown/appkit-adapter-ethers": "^1.1.2",
- "@types/react": "18.3.5",
- "@types/react-dom": "18.3.0",
- "autoprefixer": "10.4.20",
- "eslint": "8.57.1",
- "eslint-config-next": "14.2.7",
- "ethers": "^6.13.2",
- "framer-motion": "^11.7.0",
- "next": "14.2.12",
- "next-seo": "^6.6.0",
- "postcss": "8.4.44",
- "react": "18.3.1",
- "react-device-detect": "^2.2.3",
- "react-dom": "18.3.1",
- "react-icons": "^5.3.0",
- "typescript": "5.5.4"
- },
- "devDependencies": {
- "@testing-library/jest-dom": "^6.5.0",
- "@testing-library/react": "^16.0.1",
- "@types/jest": "^29.5.13",
- "@types/node": "22.5.2",
- "eslint-config-prettier": "^9.1.0",
- "eslint-plugin-prettier": "^5.2.1",
- "jest": "^29.7.0",
- "jest-environment-jsdom": "^29.7.0",
- "prettier": "^3.3.3",
- "ts-jest": "^29.1.0",
- "ts-node": "^10.9.2"
- }
-}
-
-```
-
-### pnpm-lock.yaml
-
-```yaml
-lockfileVersion: '9.0'
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false
-
-importers:
-
- .:
- dependencies:
- '@chakra-ui/icons':
- specifier: ^2.1.1
- version: 2.2.4(@chakra-ui/react@2.10.2(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(framer-motion@11.11.8(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
- '@chakra-ui/next-js':
- specifier: ^2.2.0
- version: 2.4.2(@chakra-ui/react@2.10.2(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(framer-motion@11.11.8(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(next@14.2.12(@babel/core@7.25.8)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
- '@chakra-ui/react':
- specifier: ^2.8.2
- version: 2.10.2(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(framer-motion@11.11.8(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
- '@coinbase/wallet-sdk':
- specifier: 4.0.3
- version: 4.0.3
- '@emotion/react':
- specifier: ^11.13.3
- version: 11.13.3(@types/react@18.3.5)(react@18.3.1)
- '@emotion/styled':
- specifier: ^11.13.0
- version: 11.13.0(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(react@18.3.1)
- '@reown/appkit':
- specifier: ^1.1.2
-```
-
-[This file was cut: it has more than 500 lines]
-
-```
-
-## public
-
-
-### public/favicon.ico
-
-```
-[This is an image file]
-```
-
-### public/huangshan.png
-
-```
-[This is an image file]
-```
-
-## src
-
-
-## src/__tests__
-
-
-### src/__tests__/Header.test.tsx
-
-```
-import React from 'react'
-import { render, screen } from '@testing-library/react'
-import '@testing-library/jest-dom'
-import { Header } from '../components/layout/Header'
-
-jest.mock('@reown/appkit/react', () => ({
- useAppKitAccount: () => ({ isConnected: false }),
-}))
-
-describe('Header', () => {
- it('renders the site name', () => {
- render()
- const siteName = screen.getByText('Genji')
- expect(siteName).toBeInTheDocument()
- })
-})
-
-```
-
-### src/__tests__/index.test.tsx
-
-```
-import React from 'react'
-import { render, screen } from '@testing-library/react'
-import '@testing-library/jest-dom'
-import Home from '../pages/index'
-
-jest.mock('@reown/appkit/react', () => ({
- useAppKitAccount: () => ({ address: null, isConnected: false, caipAddress: null }),
- useAppKitProvider: () => ({ walletProvider: null }),
-}))
-
-jest.mock('next/router', () => ({
- useRouter() {
- return {
- route: '/',
- pathname: '',
- query: '',
- asPath: '',
- }
- },
-}))
-
-describe('Home page', () => {
- it('renders the login message when not connected', () => {
- render()
- expect(
- screen.getByText(/You can login with your email, Google, or with one of many wallets suported by Reown\./)
- ).toBeInTheDocument()
- })
-
- it('renders the mint button', () => {
- render()
- expect(screen.getByRole('button', { name: /Mint/i })).toBeInTheDocument()
- })
-})
-
-```
-
-## src/components
-
-
-## src/components/layout
-
-
-### src/components/layout/ErrorBoundary.tsx
-
-```
-import React, { ErrorInfo, ReactNode } from 'react'
-import { mobileModel, mobileVendor } from 'react-device-detect'
-
-interface Props {
- children: ReactNode
-}
-
-interface State {
- hasError: boolean
- deviceInfo: string
-}
-
-class ErrorBoundary extends React.Component {
- constructor(props: Props) {
- super(props)
- this.state = { hasError: false, deviceInfo: '' }
- }
-
- static getDerivedStateFromError(error: Error): State {
- return { hasError: true, deviceInfo: '' }
- }
-
- componentDidCatch(error: Error, errorInfo: ErrorInfo) {
- console.error('Error caught by ErrorBoundary:', error, errorInfo)
- }
-
- componentDidMount() {
- const deviceInfo = `${mobileVendor} ${mobileModel}`
- this.setState({ deviceInfo })
- }
-
- render() {
- if (this.state.hasError) {
- return (
- <>
- All apologies, the app is not yet available on this type of device.
-
- {this.state.deviceInfo}
-
- Thank you for using the app from another device.
-
-
- Feel free to report this to Julien via Element,{' '}
- Farcaster, Telegram,{' '}
- Twitter,{' '}
- Discord or{' '}
- LinkedIn.
-
- >
- )
- }
-
- return this.props.children
- }
-}
-
-export default ErrorBoundary
-
-```
-
-### src/components/layout/Head.tsx
-
-```
-import React from 'react'
-import { default as NextHead } from 'next/head'
-import { SITE_URL } from '../../utils/config'
-
-interface Props {
- title?: string
- description?: string
-}
-
-export function Head({ title, description }: Props) {
- const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : SITE_URL
- const img = `${origin}/huangshan.png`
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-```
-
-### src/components/layout/Header.tsx
-
-```
-import React from 'react'
-import {
- Flex,
- useColorModeValue,
- Spacer,
- Heading,
- Box,
- Link,
- Icon,
- Button,
- MenuList,
- MenuItem,
- Menu,
- MenuButton,
- IconButton,
-} from '@chakra-ui/react'
-import { LinkComponent } from './LinkComponent'
-import { ThemeSwitcher } from './ThemeSwitcher'
-import { HeadingComponent } from './HeadingComponent'
-import { SITE_NAME } from '../../utils/config'
-import { FaGithub } from 'react-icons/fa'
-import { Web3Modal } from '../../context/web3modal'
-import { HamburgerIcon } from '@chakra-ui/icons'
-
-interface Props {
- className?: string
-}
-
-export function Header(props: Props) {
- const className = props.className ?? ''
-
- return (
-
-
-
- {SITE_NAME}
-
-
-
-
-
-
-
- {/* */}{' '}
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-```
-
-### src/components/layout/HeadingComponent.tsx
-
-```
-import { ReactNode } from 'react'
-import { Heading } from '@chakra-ui/react'
-
-interface Props {
- as: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'
- children: ReactNode
- className?: string
-}
-
-export function HeadingComponent(props: Props) {
- const className = props.className ?? ''
- let size
- switch (props.as) {
- case 'h1':
- size = props.size ?? '2xl'
- break
- case 'h2':
- size = props.size ?? 'xl'
- break
- case 'h3':
- size = props.size ?? 'lg'
- break
- case 'h4':
- size = props.size ?? 'md'
- break
- case 'h5':
- size = props.size ?? 'sm'
- break
- case 'h6':
- size = props.size ?? 'xs'
- break
- }
-
- return (
-
- {props.children}
-
- )
-}
-
-```
-
-### src/components/layout/LinkComponent.tsx
-
-```
-import React, { ReactNode } from 'react'
-import NextLink from 'next/link'
-import { Link, useColorModeValue } from '@chakra-ui/react'
-import { THEME_COLOR_SCHEME } from '../../utils/config'
-
-interface Props {
- href: string
- children: ReactNode
- isExternal?: boolean
- className?: string
-}
-
-export function LinkComponent(props: Props) {
- const className = props.className ?? ''
- const isExternal = props.href.match(/^([a-z0-9]*:|.{0})\/\/.*$/) || props.isExternal
- const color = useColorModeValue(`${THEME_COLOR_SCHEME}.600`, `${THEME_COLOR_SCHEME}.400`)
-
- if (isExternal) {
- return (
-
- {props.children}
-
- )
- }
-
- return (
-
- {props.children}
-
- )
-}
-
-```
-
-### src/components/layout/Seo.tsx
-
-```
-import React from 'react'
-import { SITE_DESCRIPTION, SITE_NAME, SITE_URL, SOCIAL_TWITTER } from '../../utils/config'
-import { DefaultSeo } from 'next-seo'
-
-export function Seo() {
- const origin = typeof window !== 'undefined' && window.location.origin ? window.location.origin : SITE_URL
-
- return (
-
- )
-}
-
-```
-
-### src/components/layout/ThemeSwitcher.tsx
-
-```
-import React from 'react'
-import { Box, useColorMode } from '@chakra-ui/react'
-import { MoonIcon, SunIcon } from '@chakra-ui/icons'
-
-interface Props {
- className?: string
-}
-
-export function ThemeSwitcher(props: Props) {
- const className = props.className ?? ''
- const { colorMode, toggleColorMode } = useColorMode()
-
- return (
-
- {colorMode === 'light' ? : }
-
- )
-}
-
-```
-
-### src/components/layout/index.tsx
-
-```
-import { Web3Modal } from '../../context/web3modal'
-import { ReactNode } from 'react'
-import { Box, Container } from '@chakra-ui/react'
-import { Header } from './Header'
-
-interface Props {
- children?: ReactNode
-}
-
-export default function RootLayout({ children }: Props) {
- return (
-
-
-
- {children}
-
-
- )
-}
-
-```
-
-## src/context
-
-
-### src/context/web3modal.tsx
-
-```
-'use client'
-import React, { ReactNode, createContext, useContext } from 'react'
-import { createAppKit, useAppKitProvider } from '@reown/appkit/react'
-import { EthersAdapter } from '@reown/appkit-adapter-ethers'
-import {
- sepolia,
- optimism,
- zksync,
- base,
- arbitrum,
- gnosis,
- polygon,
- polygonZkEvm,
- mantle,
- celo,
- avalanche,
- degen,
-} from '@reown/appkit/networks'
-
-const projectId = process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID || ''
-
-// https://docs.reown.com/appkit/react/core/custom-networks
-
-const metadata = {
- name: 'Genji',
- description: 'Next.js + Web3 Modal + Ethers.js + Chakra UI',
- url: 'https://genji.netlify.app',
- icons: ['./favicon.ico'],
-}
-
-createAppKit({
- adapters: [new EthersAdapter()],
- metadata,
- networks: [sepolia, optimism, zksync, base, arbitrum, gnosis, polygon, polygonZkEvm, mantle, celo, avalanche, degen],
- defaultNetwork: sepolia,
- projectId,
- features: {
- email: true,
- socials: ['google', 'farcaster', 'github'],
- },
-})
-
-const AppKitContext = createContext | null>(null)
-
-export function Web3Modal({ children }: { children: ReactNode }) {
- const appKitProvider = useAppKitProvider('eip155:11155111' as any)
-
- return {children}
-}
-
-export function useAppKit() {
- const context = useContext(AppKitContext)
- if (!context) {
- throw new Error('useAppKit must be used within a Web3Modal')
- }
- return context
-}
-
-```
-
-## src/hooks
-
-
-### src/hooks/useIsMounted.tsx
-
-```
-import { useState, useEffect } from 'react'
-
-export function useIsMounted(): boolean {
- let [isMounted, setIsMounted] = useState(false)
-
- useEffect(() => {
- setIsMounted(true)
- }, [])
-
- return isMounted
-}
-
-```
-
-## src/pages
-
-
-### src/pages/_app.tsx
-
-```
-import type { AppProps } from 'next/app'
-import Layout from '../components/layout'
-import { useEffect } from 'react'
-import { ChakraProvider } from '@chakra-ui/react'
-import { Seo } from '../components/layout/Seo'
-import { ERC20_CONTRACT_ADDRESS } from '../utils/erc20'
-import { useIsMounted } from '../hooks/useIsMounted'
-import ErrorBoundary from '../components/layout/ErrorBoundary'
-
-export default function App({ Component, pageProps }: AppProps) {
- useEffect(() => {
- console.log('contract address:', ERC20_CONTRACT_ADDRESS)
- }, [])
- const isMounted = useIsMounted()
-
- return (
- <>
-
-
-
- {isMounted && (
-
-
-
- )}
-
-
- >
- )
-}
-
-```
-
-### src/pages/_document.tsx
-
-```
-import { Html, Head, Main, NextScript } from 'next/document'
-import { ColorModeScript } from '@chakra-ui/react'
-
-export default function Document() {
- return (
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-```
-
-## src/pages/api
-
-
-### src/pages/api/faucet.ts
-
-```typescript
-import type { NextApiRequest, NextApiResponse } from 'next'
-import { ethers } from 'ethers'
-
-export default async function handler(req: NextApiRequest, res: NextApiResponse) {
- if (req.method !== 'POST') {
- return res.status(405).json({ message: 'Method Not Allowed' })
- }
-
- const { address } = req.body
-
- if (!address) {
- return res.status(400).json({ message: 'Address is required' })
- }
-
- try {
- const customProvider = new ethers.JsonRpcProvider(process.env.NEXT_PUBLIC_RPC_ENDPOINT_URL)
- const pKey = process.env.NEXT_PUBLIC_SIGNER_PRIVATE_KEY
-
- if (!pKey) {
- throw new Error('Faucet private key is not set')
- }
-
- const specialSigner = new ethers.Wallet(pKey, customProvider)
- const tx = await specialSigner.sendTransaction({
- to: address,
- value: ethers.parseEther('0.025'),
- })
-
- let receipt: ethers.TransactionReceipt | null = null
- try {
- receipt = await tx.wait(1)
- } catch (waitError) {
- console.error('Error waiting for transaction:', waitError)
- return res.status(500).json({ message: 'Transaction failed or was reverted' })
- }
-
- if (receipt === null) {
- return res.status(500).json({ message: 'Transaction was not mined within the expected time' })
- }
-
- res.status(200).json({
- message: 'Faucet transaction successful',
- txHash: receipt.hash,
- })
- } catch (error) {
- console.error('Faucet error:', error)
- if (error instanceof Error) {
- return res.status(500).json({ message: `Internal server error: ${error.message}` })
- }
- res.status(500).json({ message: 'Internal server error' })
- }
-}
-
-```
-
-### src/pages/index.tsx
-
-```
-import * as React from 'react'
-import { Text, Button, useToast, Box } from '@chakra-ui/react'
-import { useState, useEffect } from 'react'
-import { BrowserProvider, Contract, Eip1193Provider, parseEther } from 'ethers'
-// import { useAppKitAccount, useAppKitProvider, useWalletInfo } from '@reown/appkit/react'
-import { useAppKitAccount, useAppKitProvider } from '@reown/appkit/react'
-import { ERC20_CONTRACT_ADDRESS, ERC20_CONTRACT_ABI } from '../utils/erc20'
-import { LinkComponent } from '../components/layout/LinkComponent'
-import { ethers } from 'ethers'
-import { Head } from '../components/layout/Head'
-import { SITE_NAME, SITE_DESCRIPTION } from '../utils/config'
-
-export default function Home() {
- const [isLoading, setIsLoading] = useState(false)
- const [txLink, setTxLink] = useState()
- const [txHash, setTxHash] = useState()
- const [balance, setBalance] = useState('0')
- const [network, setNetwork] = useState('Unknown')
- // const [loginType, setLoginType] = useState('Not connected')
-
- const { address, isConnected, caipAddress } = useAppKitAccount()
- const { walletProvider } = useAppKitProvider('eip155')
- // const { walletInfo } = useWalletInfo()
- const toast = useToast()
-
- useEffect(() => {
- if (isConnected) {
- setTxHash(undefined)
- getNetwork()
- // updateLoginType()
- getBal()
- console.log('user address:', address)
- console.log('erc20 contract address:', ERC20_CONTRACT_ADDRESS)
- // console.log('walletInfo:', walletInfo)
- }
- }, [isConnected, address, caipAddress])
-
- const getBal = async () => {
- if (isConnected && walletProvider) {
- const ethersProvider = new BrowserProvider(walletProvider as any)
- const balance = await ethersProvider.getBalance(address as any)
-
- const ethBalance = ethers.formatEther(balance)
- console.log('bal:', Number(parseFloat(ethBalance).toFixed(5)))
- setBalance(parseFloat(ethBalance).toFixed(5))
- if (ethBalance !== '0') {
- return Number(ethBalance)
- } else {
- return 0
- }
- } else {
- return 0
- }
- }
-
- const getNetwork = async () => {
- if (walletProvider) {
- const ethersProvider = new BrowserProvider(walletProvider as any)
- const network = await ethersProvider.getNetwork()
- const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
- setNetwork(capitalize(network.name))
- }
- }
-
- // const updateLoginType = async () => {
- // try {
- // if (walletInfo != undefined) {
- // setLoginType(walletInfo.name ? walletInfo.name : 'Unknown')
- // }
- // } catch (error) {
- // console.error('Error getting login type:', error)
- // setLoginType('Unknown')
- // }
- // }
-
- const openEtherscan = () => {
- if (address) {
- const baseUrl =
- caipAddress === 'eip155:11155111:' ? 'https://sepolia.etherscan.io/address/' : 'https://etherscan.io/address/'
- window.open(baseUrl + address, '_blank')
- }
- }
-
- const faucetTx = async () => {
- try {
- const response = await fetch('/api/faucet', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ address }),
- })
- const data = await response.json()
- if (!response.ok) {
- throw new Error(data.message || 'Faucet request failed')
- }
- return data.txHash
- } catch (error) {
- console.error('Faucet error:', error)
- throw error
- }
- }
-
- const doSomething = async () => {
- setTxHash(undefined)
- try {
- if (!isConnected) {
- toast({
- title: 'Not connected yet',
- description: 'Please connect your wallet, my friend.',
- status: 'error',
- position: 'bottom',
- variant: 'subtle',
- duration: 9000,
- isClosable: true,
- })
- return
- }
- if (walletProvider) {
- setIsLoading(true)
- setTxHash('')
- setTxLink('')
- const ethersProvider = new BrowserProvider(walletProvider as Eip1193Provider)
- const signer = await ethersProvider.getSigner()
-
- const erc20 = new Contract(ERC20_CONTRACT_ADDRESS, ERC20_CONTRACT_ABI, signer)
-
- ///// Send ETH if needed /////
- const bal = await getBal()
- console.log('bal:', bal)
- if (bal < 0.025) {
- const faucetTxHash = await faucetTx()
- console.log('faucet tx:', faucetTxHash)
- const bal = await getBal()
- console.log('bal:', bal)
- }
- ///// Call /////
- const call = await erc20.mint(parseEther('10000')) // 0.000804454399826656 ETH // https://sepolia.etherscan.io/tx/0x687e32332965aa451abe45f89c9fefc4b5afe6e99c95948a300565f16a212d7b
-
- let receipt: ethers.ContractTransactionReceipt | null = null
- try {
- receipt = await call.wait()
- } catch (error) {
- console.error('Error waiting for transaction:', error)
- throw new Error('Transaction failed or was reverted')
- }
-
- if (receipt === null) {
- throw new Error('Transaction receipt is null')
- }
-
- console.log('tx:', receipt)
- setTxHash(receipt.hash)
- setTxLink('https://sepolia.etherscan.io/tx/' + receipt.hash)
- setIsLoading(false)
- toast({
- title: 'Successful tx',
- description: 'Well done! 🎉',
- status: 'success',
- position: 'bottom',
- variant: 'subtle',
- duration: 20000,
- isClosable: true,
- })
- await getBal()
- }
- } catch (e) {
- setIsLoading(false)
- console.error('Error in doSomething:', e)
- toast({
- title: 'Woops',
- description: e instanceof Error ? e.message : 'Something went wrong...',
- status: 'error',
- position: 'bottom',
- variant: 'subtle',
- duration: 9000,
- isClosable: true,
- })
- }
- }
-
- return (
- <>
-
-
- {!isConnected ? (
- <>
- You can login with your email, Google, or with one of many wallets suported by Reown.
-
- >
- ) : (
-
-
- Network: {network}
-
- {/*
- Login type: {loginType}
- */}
-
- Balance: {balance} ETH
-
-
- Address: {address || 'Not connected'}
-
-
- )}
-
- {txHash && isConnected && (
-
- {txHash}
-
- )}{' '}
-
- >
- )
-}
-
-```
-
-## src/pages/new
-
-
-### src/pages/new/index.tsx
-
-```
-import { Text, Button, useToast } from '@chakra-ui/react'
-
-export default function New() {
- return (
- <>
-
- A brand new page! 😋
-
- >
- )
-}
-
-```
-
-## src/utils
-
-
-### src/utils/config.ts
-
-```typescript
-import { ThemingProps } from '@chakra-ui/react'
-export const SITE_DESCRIPTION = 'W3HC Next.js app template'
-export const SITE_NAME = 'Genji'
-export const SITE_URL = 'https://genji-app.netlify.app'
-
-export const THEME_INITIAL_COLOR = 'system'
-export const THEME_COLOR_SCHEME: ThemingProps['colorScheme'] = 'blue'
-export const THEME_CONFIG = {
- initialColorMode: THEME_INITIAL_COLOR,
-}
-
-export const SOCIAL_TWITTER = 'w3hc8'
-export const SOCIAL_GITHUB = 'w3hc/genji'
-
-export const SERVER_SESSION_SETTINGS = {
- cookieName: SITE_NAME,
- password: process.env.SESSION_PASSWORD ?? 'UPDATE_TO_complex_password_at_least_32_characters_long',
- cookieOptions: {
- secure: process.env.NODE_ENV === 'production',
- },
-}
-
-```
-
-### src/utils/erc20.ts
-
-```typescript
-// contract used in the example app: https://github.com/w3hc/w3hc-hardhat-template/
-export const ERC20_CONTRACT_ADDRESS = '0xF57cE903E484ca8825F2c1EDc7F9EEa3744251eB' // Sepolia
-// export const ERC20_CONTRACT_ADDRESS = '0x80Fae255a5261Ca183668259382A37789e86f92F' // OP Sepolia
-export const ERC20_CONTRACT_ABI = [
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '_initialSupply',
- type: 'uint256',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'constructor',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'owner',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'value',
- type: 'uint256',
- },
- ],
- name: 'Approval',
- type: 'event',
- },
- {
- anonymous: false,
- inputs: [
- {
- indexed: true,
- internalType: 'address',
- name: 'from',
- type: 'address',
- },
- {
- indexed: true,
- internalType: 'address',
- name: 'to',
- type: 'address',
- },
- {
- indexed: false,
- internalType: 'uint256',
- name: 'value',
- type: 'uint256',
- },
- ],
- name: 'Transfer',
- type: 'event',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'owner',
- type: 'address',
- },
- {
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- ],
- name: 'allowance',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'amount',
- type: 'uint256',
- },
- ],
- name: 'approve',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'account',
- type: 'address',
- },
- ],
- name: 'balanceOf',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'decimals',
- outputs: [
- {
- internalType: 'uint8',
- name: '',
- type: 'uint8',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'subtractedValue',
- type: 'uint256',
- },
- ],
- name: 'decreaseAllowance',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'spender',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'addedValue',
- type: 'uint256',
- },
- ],
- name: 'increaseAllowance',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'uint256',
- name: '_amount',
- type: 'uint256',
- },
- ],
- name: 'mint',
- outputs: [],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [],
- name: 'name',
- outputs: [
- {
- internalType: 'string',
- name: '',
- type: 'string',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'symbol',
- outputs: [
- {
- internalType: 'string',
- name: '',
- type: 'string',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [],
- name: 'totalSupply',
- outputs: [
- {
- internalType: 'uint256',
- name: '',
- type: 'uint256',
- },
- ],
- stateMutability: 'view',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'to',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'amount',
- type: 'uint256',
- },
- ],
- name: 'transfer',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
- {
- inputs: [
- {
- internalType: 'address',
- name: 'from',
- type: 'address',
- },
- {
- internalType: 'address',
- name: 'to',
- type: 'address',
- },
- {
- internalType: 'uint256',
- name: 'amount',
- type: 'uint256',
- },
- ],
- name: 'transferFrom',
- outputs: [
- {
- internalType: 'bool',
- name: '',
- type: 'bool',
- },
- ],
- stateMutability: 'nonpayable',
- type: 'function',
- },
-]
-
-```
-
-### tsconfig.json
-
-```json
-{
- "compilerOptions": {
- "target": "es5",
- "lib": ["dom", "dom.iterable", "esnext"],
- "allowJs": true,
- "skipLibCheck": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "esModuleInterop": true,
- "module": "esnext",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "jsx": "preserve",
- "noEmit": true,
- "incremental": true
- },
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"],
- "types": ["jest", "node"],
- "esModuleInterop": true
-}
-
-```
-
-## Structure
-
-```
-├── .env.example
-├── .eslintignore
-├── .eslintrc.json
-├── .github
- └── workflows
- │ └── run-tests.yml
-├── .gitignore
-├── .next
-├── .prettierignore
-├── .prettierrc.json
-├── .swc
- └── plugins
- │ └── v7_macos_aarch64_0.106.15
-├── .vscode
- ├── extensions.json
- └── settings.json
-├── .well-known
- └── walletconnect.txt
-├── README.md
-├── genji_app_description.md
-├── jest.config.ts
-├── jest.setup.ts
-├── next.config.js
-├── package.json
-├── pnpm-lock.yaml
-├── public
- ├── favicon.ico
- └── huangshan.png
-├── src
- ├── __tests__
- │ ├── Header.test.tsx
- │ └── index.test.tsx
- ├── components
- │ └── layout
- │ │ ├── ErrorBoundary.tsx
- │ │ ├── Head.tsx
- │ │ ├── Header.tsx
- │ │ ├── HeadingComponent.tsx
- │ │ ├── LinkComponent.tsx
- │ │ ├── Seo.tsx
- │ │ ├── ThemeSwitcher.tsx
- │ │ └── index.tsx
- ├── context
- │ └── web3modal.tsx
- ├── hooks
- │ └── useIsMounted.tsx
- ├── pages
- │ ├── _app.tsx
- │ ├── _document.tsx
- │ ├── api
- │ │ └── faucet.ts
- │ ├── index.tsx
- │ └── new
- │ │ └── index.tsx
- └── utils
- │ ├── config.ts
- │ └── erc20.ts
-└── tsconfig.json
-```
-
-Timestamp: Nov 02 2024 04:04:09 PM UTC
\ No newline at end of file
diff --git a/jest.config.ts b/jest.config.ts
index 0565e15..4b8aae4 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -6,12 +6,10 @@ const createJestConfig = nextJest({
})
const customJestConfig: Config = {
- preset: 'ts-jest',
setupFilesAfterEnv: ['/jest.setup.ts'],
testEnvironment: 'jest-environment-jsdom',
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
- transform: {
- '^.+\\.(ts|tsx)$': 'ts-jest',
+ moduleNameMapper: {
+ '^@/(.*)$': '/src/$1',
},
}
diff --git a/jest.setup.ts b/jest.setup.ts
index c44951a..dba374e 100644
--- a/jest.setup.ts
+++ b/jest.setup.ts
@@ -1 +1,13 @@
import '@testing-library/jest-dom'
+
+// Just mock Reown AppKit as it's essential for the app to render
+jest.mock('@reown/appkit/react', () => ({
+ useAppKitAccount: () => ({
+ address: null,
+ isConnected: false,
+ caipAddress: null,
+ }),
+ useAppKitProvider: () => ({
+ walletProvider: null,
+ }),
+}))
diff --git a/package.json b/package.json
index a7f5f9c..3555107 100644
--- a/package.json
+++ b/package.json
@@ -7,15 +7,16 @@
"build": "next build",
"start": "next start",
"lint": "next lint",
- "test": "jest",
- "test:watch": "jest --watch",
+ "test": "jest --config jest.config.ts",
+ "test:watch": "jest --config jest.config.ts --watch",
+ "test:coverage": "jest --config jest.config.ts --coverage",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\""
},
"dependencies": {
- "@chakra-ui/icons": "^2.1.1",
- "@chakra-ui/next-js": "^2.2.0",
- "@chakra-ui/react": "^2.8.2",
+ "@chakra-ui/icons": "^2.2.4",
+ "@chakra-ui/next-js": "^2.4.2",
+ "@chakra-ui/react": "^2.10.2",
"@coinbase/wallet-sdk": "4.0.3",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
@@ -26,8 +27,8 @@
"autoprefixer": "10.4.20",
"eslint": "8.57.1",
"eslint-config-next": "14.2.7",
- "ethers": "^6.13.2",
- "framer-motion": "^11.7.0",
+ "ethers": "^6.13.4",
+ "framer-motion": "^11.11.8",
"next": "14.2.12",
"next-seo": "^6.6.0",
"postcss": "8.4.44",
@@ -47,7 +48,7 @@
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"prettier": "^3.3.3",
- "ts-jest": "^29.1.0",
+ "ts-jest": "^29.2.5",
"ts-node": "^10.9.2"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cdc1f56..3b35fac 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,13 +9,13 @@ importers:
.:
dependencies:
'@chakra-ui/icons':
- specifier: ^2.1.1
+ specifier: ^2.2.4
version: 2.2.4(@chakra-ui/react@2.10.2(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(framer-motion@11.11.8(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@chakra-ui/next-js':
- specifier: ^2.2.0
+ specifier: ^2.4.2
version: 2.4.2(@chakra-ui/react@2.10.2(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(framer-motion@11.11.8(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(next@14.2.12(@babel/core@7.25.8)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
'@chakra-ui/react':
- specifier: ^2.8.2
+ specifier: ^2.10.2
version: 2.10.2(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@emotion/styled@11.13.0(@emotion/react@11.13.3(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(react@18.3.1))(@types/react@18.3.5)(framer-motion@11.11.8(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@coinbase/wallet-sdk':
specifier: 4.0.3
@@ -48,10 +48,10 @@ importers:
specifier: 14.2.7
version: 14.2.7(eslint@8.57.1)(typescript@5.5.4)
ethers:
- specifier: ^6.13.2
+ specifier: ^6.13.4
version: 6.13.4
framer-motion:
- specifier: ^11.7.0
+ specifier: ^11.11.8
version: 11.11.8(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next:
specifier: 14.2.12
@@ -106,7 +106,7 @@ importers:
specifier: ^3.3.3
version: 3.3.3
ts-jest:
- specifier: ^29.1.0
+ specifier: ^29.2.5
version: 29.2.5(@babel/core@7.25.8)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.8))(jest@29.7.0(@types/node@22.5.2)(babel-plugin-macros@3.1.0)(ts-node@10.9.2(@types/node@22.5.2)(typescript@5.5.4)))(typescript@5.5.4)
ts-node:
specifier: ^10.9.2
@@ -131,6 +131,10 @@ packages:
resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==}
engines: {node: '>=6.9.0'}
+ '@babel/code-frame@7.26.2':
+ resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/compat-data@7.25.8':
resolution: {integrity: sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==}
engines: {node: '>=6.9.0'}
@@ -173,6 +177,10 @@ packages:
resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.25.9':
+ resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.25.7':
resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==}
engines: {node: '>=6.9.0'}
@@ -285,6 +293,10 @@ packages:
resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==}
engines: {node: '>=6.9.0'}
+ '@babel/runtime@7.26.0':
+ resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/template@7.25.7':
resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==}
engines: {node: '>=6.9.0'}
@@ -2960,6 +2972,9 @@ packages:
picocolors@1.1.0:
resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==}
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
@@ -3917,6 +3932,12 @@ snapshots:
'@babel/highlight': 7.25.7
picocolors: 1.1.0
+ '@babel/code-frame@7.26.2':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.25.9
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
'@babel/compat-data@7.25.8': {}
'@babel/core@7.25.8':
@@ -3984,6 +4005,8 @@ snapshots:
'@babel/helper-validator-identifier@7.25.7': {}
+ '@babel/helper-validator-identifier@7.25.9': {}
+
'@babel/helper-validator-option@7.25.7': {}
'@babel/helpers@7.25.7':
@@ -4091,6 +4114,10 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
+ '@babel/runtime@7.26.0':
+ dependencies:
+ regenerator-runtime: 0.14.1
+
'@babel/template@7.25.7':
dependencies:
'@babel/code-frame': 7.25.7
@@ -5017,8 +5044,8 @@ snapshots:
'@testing-library/dom@10.4.0':
dependencies:
- '@babel/code-frame': 7.25.7
- '@babel/runtime': 7.25.7
+ '@babel/code-frame': 7.26.2
+ '@babel/runtime': 7.26.0
'@types/aria-query': 5.0.4
aria-query: 5.3.0
chalk: 4.1.2
@@ -7732,6 +7759,8 @@ snapshots:
picocolors@1.1.0: {}
+ picocolors@1.1.1: {}
+
picomatch@2.3.1: {}
pino-abstract-transport@0.5.0:
diff --git a/.well-known/walletconnect.txt b/public/.well-known/walletconnect.txt
similarity index 100%
rename from .well-known/walletconnect.txt
rename to public/.well-known/walletconnect.txt
diff --git a/src/__tests__/Header.test.tsx b/src/__tests__/Header.test.tsx
index 392114a..fd731b7 100644
--- a/src/__tests__/Header.test.tsx
+++ b/src/__tests__/Header.test.tsx
@@ -1,16 +1,11 @@
import React from 'react'
-import { render, screen } from '@testing-library/react'
+import { render, screen } from '../utils/test-utils'
+import { Header } from '@/components/Header'
import '@testing-library/jest-dom'
-import { Header } from '../components/layout/Header'
-
-jest.mock('@reown/appkit/react', () => ({
- useAppKitAccount: () => ({ isConnected: false }),
-}))
describe('Header', () => {
- it('renders the site name', () => {
+ it('exists in the document', () => {
render()
- const siteName = screen.getByText('Genji')
- expect(siteName).toBeInTheDocument()
+ expect(document.querySelector('header')).toBeInTheDocument()
})
})
diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx
index db15fbb..9e2414a 100644
--- a/src/__tests__/index.test.tsx
+++ b/src/__tests__/index.test.tsx
@@ -1,34 +1,12 @@
import React from 'react'
-import { render, screen } from '@testing-library/react'
+import { render, screen } from '../utils/test-utils'
import '@testing-library/jest-dom'
-import Home from '../pages/index'
-
-jest.mock('@reown/appkit/react', () => ({
- useAppKitAccount: () => ({ address: null, isConnected: false, caipAddress: null }),
- useAppKitProvider: () => ({ walletProvider: null }),
-}))
-
-jest.mock('next/router', () => ({
- useRouter() {
- return {
- route: '/',
- pathname: '',
- query: '',
- asPath: '',
- }
- },
-}))
+import Home from '@/pages/index'
describe('Home page', () => {
- it('renders the login message when not connected', () => {
- render()
- expect(
- screen.getByText(/You can login with your email, Google, or with one of many wallets suported by Reown\./)
- ).toBeInTheDocument()
- })
-
it('renders the mint button', () => {
render()
- expect(screen.getByRole('button', { name: /Mint/i })).toBeInTheDocument()
+ const button = screen.getByRole('button', { name: /mint/i })
+ expect(button).toBeInTheDocument()
})
})
diff --git a/src/components/layout/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx
similarity index 100%
rename from src/components/layout/ErrorBoundary.tsx
rename to src/components/ErrorBoundary.tsx
diff --git a/src/components/layout/Head.tsx b/src/components/Head.tsx
similarity index 94%
rename from src/components/layout/Head.tsx
rename to src/components/Head.tsx
index c3cfe87..dce7435 100644
--- a/src/components/layout/Head.tsx
+++ b/src/components/Head.tsx
@@ -1,6 +1,6 @@
import React from 'react'
import { default as NextHead } from 'next/head'
-import { SITE_URL } from '../../utils/config'
+import { SITE_URL } from '../utils/config'
interface Props {
title?: string
diff --git a/src/components/layout/Header.tsx b/src/components/Header.tsx
similarity index 86%
rename from src/components/layout/Header.tsx
rename to src/components/Header.tsx
index f0b873f..38b518d 100644
--- a/src/components/layout/Header.tsx
+++ b/src/components/Header.tsx
@@ -17,9 +17,9 @@ import {
import { LinkComponent } from './LinkComponent'
import { ThemeSwitcher } from './ThemeSwitcher'
import { HeadingComponent } from './HeadingComponent'
-import { SITE_NAME } from '../../utils/config'
+import { SITE_NAME } from '../utils/config'
import { FaGithub } from 'react-icons/fa'
-import { Web3Modal } from '../../context/web3modal'
+import { Web3Modal } from '../context/web3modal'
import { HamburgerIcon } from '@chakra-ui/icons'
interface Props {
@@ -38,7 +38,7 @@ export function Header(props: Props) {
py={5}
mb={8}
alignItems="center">
-
+
{SITE_NAME}
@@ -48,17 +48,16 @@ export function Header(props: Props) {
- {/* */}{' '}
diff --git a/src/components/layout/HeadingComponent.tsx b/src/components/HeadingComponent.tsx
similarity index 100%
rename from src/components/layout/HeadingComponent.tsx
rename to src/components/HeadingComponent.tsx
diff --git a/src/components/LinkComponent.tsx b/src/components/LinkComponent.tsx
new file mode 100644
index 0000000..2af62bf
--- /dev/null
+++ b/src/components/LinkComponent.tsx
@@ -0,0 +1,37 @@
+import React, { ReactNode } from 'react'
+import NextLink from 'next/link'
+import { Link, useColorModeValue } from '@chakra-ui/react'
+import { THEME_COLOR_SCHEME } from '@/utils/config'
+
+interface Props {
+ href: string
+ children: ReactNode
+ isExternal?: boolean
+ className?: string
+ invisible?: boolean
+}
+
+export function LinkComponent(props: Props) {
+ const className = props.className ?? ''
+ const isExternal = props.href.match(/^([a-z0-9]*:|.{0})\/\/.*$/) || props.isExternal
+ const defaultColor = useColorModeValue(`${THEME_COLOR_SCHEME}.600`, `${THEME_COLOR_SCHEME}.400`)
+
+ // Apply invisible styling or default link styling
+ const linkStyle = props.invisible
+ ? { _hover: { color: defaultColor } }
+ : { color: '#45a2f8', _hover: { color: '#8c1c84' } }
+
+ if (isExternal) {
+ return (
+
+ {props.children}
+
+ )
+ }
+
+ return (
+
+ {props.children}
+
+ )
+}
diff --git a/src/components/layout/Seo.tsx b/src/components/Seo.tsx
similarity index 97%
rename from src/components/layout/Seo.tsx
rename to src/components/Seo.tsx
index c6301c1..fa2d4e8 100644
--- a/src/components/layout/Seo.tsx
+++ b/src/components/Seo.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import { SITE_DESCRIPTION, SITE_NAME, SITE_URL, SOCIAL_TWITTER } from '../../utils/config'
+import { SITE_DESCRIPTION, SITE_NAME, SITE_URL, SOCIAL_TWITTER } from '../utils/config'
import { DefaultSeo } from 'next-seo'
export function Seo() {
diff --git a/src/components/layout/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx
similarity index 100%
rename from src/components/layout/ThemeSwitcher.tsx
rename to src/components/ThemeSwitcher.tsx
diff --git a/src/components/layout/LinkComponent.tsx b/src/components/layout/LinkComponent.tsx
deleted file mode 100644
index de3d57b..0000000
--- a/src/components/layout/LinkComponent.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import React, { ReactNode } from 'react'
-import NextLink from 'next/link'
-import { Link, useColorModeValue } from '@chakra-ui/react'
-import { THEME_COLOR_SCHEME } from '../../utils/config'
-
-interface Props {
- href: string
- children: ReactNode
- isExternal?: boolean
- className?: string
-}
-
-export function LinkComponent(props: Props) {
- const className = props.className ?? ''
- const isExternal = props.href.match(/^([a-z0-9]*:|.{0})\/\/.*$/) || props.isExternal
- const color = useColorModeValue(`${THEME_COLOR_SCHEME}.600`, `${THEME_COLOR_SCHEME}.400`)
-
- if (isExternal) {
- return (
-
- {props.children}
-
- )
- }
-
- return (
-
- {props.children}
-
- )
-}
diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx
index 352de52..5bcb645 100644
--- a/src/components/layout/index.tsx
+++ b/src/components/layout/index.tsx
@@ -1,7 +1,7 @@
import { Web3Modal } from '../../context/web3modal'
import { ReactNode } from 'react'
import { Box, Container } from '@chakra-ui/react'
-import { Header } from './Header'
+import { Header } from '../Header'
interface Props {
children?: ReactNode
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index bea61e9..4e2e38f 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -2,10 +2,11 @@ import type { AppProps } from 'next/app'
import Layout from '../components/layout'
import { useEffect } from 'react'
import { ChakraProvider } from '@chakra-ui/react'
-import { Seo } from '../components/layout/Seo'
+import { DefaultSeo } from 'next-seo'
+import { SITE_NAME, SITE_DESCRIPTION, SITE_URL, SOCIAL_TWITTER } from '../utils/config'
import { ERC20_CONTRACT_ADDRESS } from '../utils/erc20'
import { useIsMounted } from '../hooks/useIsMounted'
-import ErrorBoundary from '../components/layout/ErrorBoundary'
+import ErrorBoundary from '../components/ErrorBoundary'
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
@@ -15,9 +16,41 @@ export default function App({ Component, pageProps }: AppProps) {
return (
<>
+
-
{isMounted && (
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index eeb57ed..e52e054 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -2,15 +2,19 @@ import * as React from 'react'
import { Text, Button, useToast, Box } from '@chakra-ui/react'
import { useState, useEffect } from 'react'
import { BrowserProvider, Contract, Eip1193Provider, parseEther } from 'ethers'
-// import { useAppKitAccount, useAppKitProvider, useWalletInfo } from '@reown/appkit/react'
import { useAppKitAccount, useAppKitProvider } from '@reown/appkit/react'
import { ERC20_CONTRACT_ADDRESS, ERC20_CONTRACT_ABI } from '../utils/erc20'
-import { LinkComponent } from '../components/layout/LinkComponent'
+import { LinkComponent } from '../components/LinkComponent'
import { ethers } from 'ethers'
-import { Head } from '../components/layout/Head'
+import { Head } from '../components/Head'
import { SITE_NAME, SITE_DESCRIPTION } from '../utils/config'
+import { NextSeo } from 'next-seo'
+import { SITE_URL } from '../utils/config'
export default function Home() {
+ const seoTitle = 'Genji - Web3 Application Template'
+ const seoDescription = 'A modern Web3 application template featuring Next.js, Reown, Ethers.js, and Chakra UI'
+
const [isLoading, setIsLoading] = useState(false)
const [txLink, setTxLink] = useState()
const [txHash, setTxHash] = useState()
@@ -181,6 +185,41 @@ export default function Home() {
return (
<>
+
{!isConnected ? (
diff --git a/src/pages/new/index.tsx b/src/pages/new/index.tsx
index c41a673..d3cc526 100644
--- a/src/pages/new/index.tsx
+++ b/src/pages/new/index.tsx
@@ -1,8 +1,43 @@
import { Text, Button, useToast } from '@chakra-ui/react'
+import { NextSeo } from 'next-seo'
+import { SITE_URL } from '../../utils/config'
export default function New() {
+ const seoTitle = 'New Page - Genji'
+ const seoDescription = 'Create new content and interact with the Genji Web3 template'
+
return (
<>
+
A brand new page! 😋
diff --git a/src/utils/test-utils.tsx b/src/utils/test-utils.tsx
new file mode 100644
index 0000000..5b044ac
--- /dev/null
+++ b/src/utils/test-utils.tsx
@@ -0,0 +1,18 @@
+import React from 'react'
+import { ChakraProvider } from '@chakra-ui/react'
+import { render, RenderOptions } from '@testing-library/react'
+
+// Create a custom render function that includes providers
+const customRender = (ui: React.ReactElement, options?: Omit) => {
+ const AllProviders = ({ children }: { children: React.ReactNode }) => {
+ return {children}
+ }
+
+ return render(ui, { wrapper: AllProviders, ...options })
+}
+
+// re-export everything
+export * from '@testing-library/react'
+
+// override render method
+export { customRender as render }
diff --git a/tsconfig.json b/tsconfig.json
index 18e5bb1..edb3b34 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -13,10 +13,14 @@
"isolatedModules": true,
"jsx": "preserve",
"noEmit": true,
- "incremental": true
+ "incremental": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["src/*"],
+ "src/*": ["src/*"]
+ },
+ "types": ["jest", "node", "@testing-library/jest-dom"]
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"],
- "types": ["jest", "node"],
- "esModuleInterop": true
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "jest.setup.ts", "jest.config.ts"],
+ "exclude": ["node_modules"]
}