From d84baacedf82aedbdcc378962b5059f2086550d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20B=C3=A9ranger?= Date: Sat, 2 Nov 2024 17:07:20 +0100 Subject: [PATCH] Add prettier (#47) fix prettier config --- .eslintrc.json | 8 +- .github/workflows/run-tests.yml | 27 +- .prettierignore | 44 + .prettierrc.json | 25 +- .vscode/extensions.json | 2 +- .vscode/settings.json | 23 +- genji_app_description.md | 1916 +++++++++++++++++++++++ package.json | 9 +- pnpm-lock.yaml | 77 + src/__tests__/index.test.tsx | 4 +- src/components/layout/ErrorBoundary.tsx | 3 +- src/components/layout/Header.tsx | 25 +- src/components/layout/LinkComponent.tsx | 7 +- src/context/web3modal.tsx | 15 +- src/pages/index.tsx | 3 +- 15 files changed, 2158 insertions(+), 30 deletions(-) create mode 100644 .prettierignore create mode 100644 genji_app_description.md diff --git a/.eslintrc.json b/.eslintrc.json index bffb357..f90bc04 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,9 @@ { - "extends": "next/core-web-vitals" + "extends": ["next/core-web-vitals", "prettier"], + "plugins": ["prettier"], + "rules": { + "prettier/prettier": "error", + "arrow-body-style": "off", + "prefer-arrow-callback": "off" + } } diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index e77732b..e4a8956 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,36 +1,37 @@ -name: Run Tests +name: CI on: + push: + branches: [main, master] pull_request: - branches: [main] + branches: [main, master] jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - name: Use Node.js - uses: actions/setup-node@v3 + - name: Setup Node.js + uses: actions/setup-node@v4 with: node-version: '20' - - name: Install pnpm + - name: Setup pnpm uses: pnpm/action-setup@v2 with: - version: 8 + version: '8.7.5' - name: Get pnpm store directory - id: pnpm-cache shell: bash run: | - echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - uses: actions/cache@v3 name: Setup pnpm cache with: - path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + path: ${{ env.STORE_PATH }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} restore-keys: | ${{ runner.os }}-pnpm-store- @@ -38,5 +39,11 @@ 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 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..86c19c7 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,44 @@ +# 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 \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json index bb5adcf..24c8528 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,9 +1,18 @@ { - "trailingComma": "es5", - "semi": false, - "singleQuote": true, - "printWidth": 150, - "bracketSameLine": true, - "useTabs": false, - "tabWidth": 2 -} \ No newline at end of file + "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 +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index c83e263..d7df89c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["esbenp.prettier-vscode"] + "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 1b6457c..c5a02a6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,25 @@ { + "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "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" + } } diff --git a/genji_app_description.md b/genji_app_description.md new file mode 100644 index 0000000..113059e --- /dev/null +++ b/genji_app_description.md @@ -0,0 +1,1916 @@ +# 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} + + + + + + } size={'sm'} mr={4} /> + + + Home + + + New + + + + + + {/* */}{' '} + + + + + + + + + + + ) +} + +``` + +### 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/package.json b/package.json index 492f2c1..a7f5f9c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "start": "next start", "lint": "next lint", "test": "jest", - "test:watch": "jest --watch" + "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", @@ -40,9 +42,12 @@ "@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" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 133029e..cdc1f56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,12 +90,21 @@ importers: '@types/node': specifier: 22.5.2 version: 22.5.2 + eslint-config-prettier: + specifier: ^9.1.0 + version: 9.1.0(eslint@8.57.1) + eslint-plugin-prettier: + specifier: ^5.2.1 + version: 5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3) jest: specifier: ^29.7.0 version: 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)) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0 + prettier: + specifier: ^3.3.3 + version: 3.3.3 ts-jest: specifier: ^29.1.0 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) @@ -723,6 +732,10 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@popperjs/core@2.11.8': resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} @@ -1719,6 +1732,12 @@ packages: typescript: optional: true + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} @@ -1772,6 +1791,20 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + eslint-plugin-prettier@5.2.1: + resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705: resolution: {integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==} engines: {node: '>=10'} @@ -1852,6 +1885,9 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -2975,6 +3011,15 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -3408,6 +3453,10 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + synckit@0.9.2: + resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} + engines: {node: ^14.18.0 || >=16.0.0} + system-architecture@0.1.0: resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} engines: {node: '>=18'} @@ -4617,6 +4666,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@pkgr/core@0.1.1': {} + '@popperjs/core@2.11.8': {} '@reown/appkit-adapter-ethers@1.1.2(@coinbase/wallet-sdk@4.0.3)(@ethersproject/sha2@5.7.0)(@types/react@18.3.5)(ethers@6.13.4)(react@18.3.1)(typescript@5.5.4)(zod@3.22.4)': @@ -6165,6 +6216,10 @@ snapshots: - eslint-plugin-import-x - supports-color + eslint-config-prettier@9.1.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 @@ -6252,6 +6307,15 @@ snapshots: safe-regex-test: 1.0.3 string.prototype.includes: 2.0.0 + eslint-plugin-prettier@5.2.1(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3): + dependencies: + eslint: 8.57.1 + prettier: 3.3.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.9.2 + optionalDependencies: + eslint-config-prettier: 9.1.0(eslint@8.57.1) + eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -6401,6 +6465,8 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-diff@1.3.0: {} + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -7723,6 +7789,12 @@ snapshots: prelude-ls@1.2.1: {} + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.3.3: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -8152,6 +8224,11 @@ snapshots: symbol-tree@3.2.4: {} + synckit@0.9.2: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.7.0 + system-architecture@0.1.0: {} tapable@2.2.1: {} diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index c16158f..db15fbb 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -22,7 +22,9 @@ jest.mock('next/router', () => ({ 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() + 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', () => { diff --git a/src/components/layout/ErrorBoundary.tsx b/src/components/layout/ErrorBoundary.tsx index 7e25772..977cbc4 100644 --- a/src/components/layout/ErrorBoundary.tsx +++ b/src/components/layout/ErrorBoundary.tsx @@ -42,7 +42,8 @@ class ErrorBoundary extends React.Component {

Feel free to report this to Julien via Element,{' '} Farcaster, Telegram,{' '} - Twitter, Discord or{' '} + Twitter,{' '} + Discord or{' '} LinkedIn.

diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 1e872bb..f0b873f 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -1,5 +1,19 @@ import React from 'react' -import { Flex, useColorModeValue, Spacer, Heading, Box, Link, Icon, Button, MenuList, MenuItem, Menu, MenuButton, IconButton } from '@chakra-ui/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' @@ -16,7 +30,14 @@ export function Header(props: Props) { const className = props.className ?? '' return ( - + {SITE_NAME} diff --git a/src/components/layout/LinkComponent.tsx b/src/components/layout/LinkComponent.tsx index 8dac45c..de3d57b 100644 --- a/src/components/layout/LinkComponent.tsx +++ b/src/components/layout/LinkComponent.tsx @@ -17,7 +17,12 @@ export function LinkComponent(props: Props) { if (isExternal) { return ( - + {props.children} ) diff --git a/src/context/web3modal.tsx b/src/context/web3modal.tsx index 45511b0..583a0ea 100644 --- a/src/context/web3modal.tsx +++ b/src/context/web3modal.tsx @@ -2,7 +2,20 @@ 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' +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 || '' diff --git a/src/pages/index.tsx b/src/pages/index.tsx index b235ec7..eeb57ed 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -75,7 +75,8 @@ export default function Home() { const openEtherscan = () => { if (address) { - const baseUrl = caipAddress === 'eip155:11155111:' ? 'https://sepolia.etherscan.io/address/' : 'https://etherscan.io/address/' + const baseUrl = + caipAddress === 'eip155:11155111:' ? 'https://sepolia.etherscan.io/address/' : 'https://etherscan.io/address/' window.open(baseUrl + address, '_blank') } }