Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/vite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ jobs:
node-version: '22'
- name: Build Vite Site
run: |
cd modal
npm install
npm run build
- name: Upload artifact
Expand Down
File renamed without changes.
File renamed without changes.
9 changes: 7 additions & 2 deletions eslint.config.js → modal/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,41 @@ import globals from 'globals'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default [
{ ignores: ['dist'] },
{
files: ['**/*.{js,jsx}'],
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
project: './tsconfig.json',
},
},
settings: { react: { version: '18.3' } },
plugins: {
react,
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
'@typescript-eslint': tseslint.plugin,
},
rules: {
...js.configs.recommended.rules,
...react.configs.recommended.rules,
...react.configs['jsx-runtime'].rules,
...reactHooks.configs.recommended.rules,
...tseslint.configs.recommended.rules, // 👈 TS recommended rules
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
]
]
File renamed without changes.
49 changes: 49 additions & 0 deletions modal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "hello-web3auth-modal",
"private": true,
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint:fix": "eslint --fix src",
"prettier:fix": "prettier --write .",
"preview": "vite preview"
},
"dependencies": {
"@near-js/accounts": "^2.3.1",
"@near-js/crypto": "^2.3.1",
"@near-js/providers": "^2.3.1",
"@near-js/signers": "^2.4.0",
"@near-js/tokens": "^2.2.5",
"@near-js/transactions": "^2.3.1",
"@noble/hashes": "^1.8.0",
"@scure/base": "^1.2.6",
"@web3auth/base": "^9.7.0",
"@web3auth/base-provider": "^9.7.0",
"@web3auth/modal": "^9.7.0",
"@web3auth/modal-react-hooks": "^9.7.0",
"bootstrap": "^5",
"bootstrap-icons": "^1.11.3",
"react": "^18",
"react-dom": "^18",
"react-router": "^7",
"react-router-dom": "^7.9.1"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@types/react": "^18.3.26",
"@types/react-dom": "^18.3.7",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"@typescript-eslint/parser": "^8.46.2",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.17.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"prettier": "^3.6.2",
"typescript": "^5.9.3",
"vite": "^6.3.5",
"vite-plugin-node-polyfills": "^0.24.0"
}
}
File renamed without changes.
30 changes: 30 additions & 0 deletions modal/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Navigation } from './components/navigation';
import Home from './pages/home';
import HelloNear from './pages/hello_near';
import { BrowserRouter, HashRouter, Routes, Route } from 'react-router';
import { Web3AuthProvider } from '@web3auth/modal-react-hooks';

import { NEARxWeb3Auth } from './context/provider';
import { web3AuthContextConfig } from './config';

function App() {
// Use HashRouter when deployed under a subpath (e.g., GitHub Pages)
const useHashRouter = import.meta.env.BASE_URL !== '/';
const Router = useHashRouter ? HashRouter : BrowserRouter;

return (
<Web3AuthProvider config={web3AuthContextConfig}>
<NEARxWeb3Auth>
<Router>
<Navigation />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/hello-near" element={<HelloNear />} />
</Routes>
</Router>
</NEARxWeb3Auth>
</Web3AuthProvider>
);
}

export default App;
File renamed without changes
File renamed without changes
File renamed without changes
27 changes: 27 additions & 0 deletions modal/src/components/cards.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import styles from '@/styles/app.module.css'
import { Link } from 'react-router-dom'

export const Cards = () => {
return (
<div className={styles.grid}>
<a
href="https://docs.near.org/build/web3-apps/quickstart"
className={styles.card}
target="_blank"
rel="noopener noreferrer"
>
<h2>
Near Docs <span>-&gt;</span>
</h2>
<p>Learn how this application works, and what you can build on Near.</p>
</a>

<Link to="/hello-near" className={styles.card}>
<h2>
Near Integration <span>-&gt;</span>
</h2>
<p>Discover how simple it is to interact with a Near smart contract.</p>
</Link>
</div>
)
}
76 changes: 76 additions & 0 deletions modal/src/components/navigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router';
import NearLogo from '@/assets/near-logo.svg';
import styles from '@/styles/app.module.css';

import { useNEARxWeb3Auth } from '../context/useNear';
import { useWeb3Auth } from '@web3auth/modal-react-hooks';
import { NEAR } from '@near-js/tokens';

export const Navigation = () => {
const [action, setAction] = useState<() => void>(() => () => {});
const [label, setLabel] = useState<string>('Loading...');
const [balance, setBalance] = useState<string | number>('0');

// Web3Auth Modal for login/logout
const { connect, logout, isConnected, userInfo } = useWeb3Auth();

const { walletId, nearAccount, loading } = useNEARxWeb3Auth();

useEffect(() => {
if (loading) {
setLabel('Loading...');
return;
}

if (isConnected) {
const userId = userInfo?.email || userInfo?.name || 'User';
setAction(() => logout);
setLabel(`Logout ${userId}`);
} else {
setAction(() => connect);
setLabel('Login');
setBalance('0');
}
}, [isConnected, walletId, loading, userInfo, logout, connect]);

// Fetch NEAR balance
useEffect(() => {
if (walletId && nearAccount) {
nearAccount
.getBalance()
.then((b) => {
const formatted = Number(NEAR.toDecimal(b)).toFixed(2);
setBalance(formatted);
})
.catch(() => setBalance('0'));
}
}, [walletId, nearAccount]);

return (
<nav className="navbar navbar-expand-lg">
<div className="container-fluid">
<Link to="/">
<img
src={NearLogo}
alt="NEAR"
width="30"
height="24"
className={styles.logo}
/>
</Link>
<div className="navbar-nav pt-1">
{isConnected && (
<span className="badge text-dark small">
<br />
{walletId}: {balance} Ⓝ
</span>
)}
<button className="btn btn-secondary" onClick={action}>
{label}
</button>
</div>
</div>
</nav>
);
};
36 changes: 36 additions & 0 deletions modal/src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Web3Auth Config
import { CHAIN_NAMESPACES, WEB3AUTH_NETWORK } from '@web3auth/base'
import { CommonPrivateKeyProvider } from '@web3auth/base-provider'

const chainConfig = {
chainNamespace: CHAIN_NAMESPACES.OTHER,
chainId: 'near:testnet', // NEAR mainnet
rpcTarget: 'https://rpc.testnet.near.org',
displayName: 'NEAR Mainnet',
blockExplorerUrl: 'https://nearblocks.io',
ticker: 'NEAR',
tickerName: 'NEAR',
}

const privateKeyProvider = new CommonPrivateKeyProvider({
config: { chainConfig }
})

export const web3AuthContextConfig = {
web3AuthOptions: {
clientId: 'BP1rATmBxrPQ5BK0cMry4vmcOXJwYVSElff0dnb3in004j9lFE2SI2QUlC9Sy9lkqVgzussY6QPkOXWocnoJGGI',
web3AuthNetwork: WEB3AUTH_NETWORK.SAPPHIRE_DEVNET,
chainConfig,
privateKeyProvider,
}
}

// NEAR Config
const contractPerNetwork = {
mainnet: 'hello.near-examples.near',
testnet: 'hello.near-examples.testnet',
}

export const NetworkId = 'testnet'
export const providerUrl = 'https://test.rpc.fastnear.com'
export const HelloNearContract = contractPerNetwork[NetworkId]
Loading