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
2 changes: 1 addition & 1 deletion .github/workflows/ci-lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- exchange-oracle/server
- exchange-oracle/client
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Enable corepack
run: corepack enable
- uses: actions/setup-node@v6
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
exchange_oracle_server: ${{ steps.filter.outputs.exchange_oracle_server }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0

Expand All @@ -45,7 +45,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Enable corepack
run: corepack enable
Expand All @@ -72,7 +72,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7

- name: Enable corepack
run: corepack enable
Expand Down
14 changes: 14 additions & 0 deletions campaign-launcher/client/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# General
VITE_APP_WALLETCONNECT_PROJECT_ID=replace_me
VITE_APP_DOCS_URL=https://docs.humanprotocol.org
VITE_APP_STAKING_DASHBOARD_URL=https://staking.humanprotocol.org
VITE_FOOTER_LINK_GITHUB=https://github.com/Hu-Fi/hufi
VITE_FOOTER_LINK_TELEGRAM=https://t.me/Hu_Finance
VITE_FOOTER_LINK_X=https://x.com/Hu_Finance

# Web3
VITE_APP_ENVIRONMENT=localhost
VITE_APP_SUPPORTED_CHAINS=1338
VITE_APP_EXCHANGE_ORACLE_ADDRESS=0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc
VITE_APP_RECORDING_ORACLE_ADDRESS=0x976EA74026E726554dB657fA54763abd0C3a0aa9
VITE_APP_REPUTATION_ORACLE_ADDRESS=0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65
6 changes: 6 additions & 0 deletions campaign-launcher/client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dist
.env
.env.local
.env.*.local
.yarn/install-state.gz
node_modules/.yarn-state.yml
1 change: 1 addition & 0 deletions campaign-launcher/client/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
53 changes: 53 additions & 0 deletions campaign-launcher/client/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import eslint from '@eslint/js';
import globals from 'globals';
import tseslint from 'typescript-eslint';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import reactRefreshPlugin from 'eslint-plugin-react-refresh';
import { fileURLToPath } from 'node:url';
import path from 'node:path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const config = tseslint.config(
{
ignores: ['dist', 'vite.config.ts'],
},
eslint.configs.recommended,
tseslint.configs.recommended,
{
files: ['**/*.{ts,tsx,js,jsx}'],
languageOptions: {
globals: {
...globals.browser,
...globals.es2022,
},
ecmaVersion: 2022,
sourceType: 'module',
parserOptions: {
projectService: true,
tsconfigRootDir: __dirname,
},
},
plugins: {
'react-hooks': reactHooksPlugin,
'react-refresh': reactRefreshPlugin,
},
rules: {
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-explicit-any': 'off',
quotes: [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: true },
],
},
}
);

export default config;
12 changes: 12 additions & 0 deletions campaign-launcher/client/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Marketing Campaign Launcher</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
52 changes: 52 additions & 0 deletions campaign-launcher/client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "marketing-campaign-launcher-client",
"private": true,
"version": "1.0.0",
"description": "Marketing Campaign Launcher Client",
"author": "Human Protocol",
"license": "MIT",
"packageManager": "yarn@4.10.3",
"scripts": {
"clean": "rm -rf dist",
"lint": "eslint \"**/*.{ts,tsx}\"",
"start": "vite",
"build": "vite build",
"preview": "vite preview",
"start:prod": "serve -s dist",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx}\""
},
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"@human-protocol/core": "^7.0.0",
"@human-protocol/sdk": "^7.3.0",
"@mui/icons-material": "^5.18.0",
"@mui/material": "^5.16.7",
"@reown/appkit": "^1.8.19",
"@reown/appkit-adapter-wagmi": "^1.8.19",
"@tanstack/react-query": "^5.91.3",
"ethers": "^6.16.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router-dom": "^7.13.0",
"serve": "^14.2.4",
"viem": "2.x",
"wagmi": "^3.6.4"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^22.15.16",
"@types/react": "^19.2.2",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.3.1",
"eslint": "^10.1.0",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.11",
"globals": "^16.3.0",
"prettier": "^3.8.1",
"typescript": "^5.6.3",
"typescript-eslint": "^8.57.0",
"vite": "^6.2.4",
"vite-plugin-node-polyfills": "^0.25.0"
}
}
2 changes: 2 additions & 0 deletions campaign-launcher/client/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
User-agent: *
Disallow:
35 changes: 35 additions & 0 deletions campaign-launcher/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ReactNode } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import { useAccount } from 'wagmi';

import CreateCampaignPage from '@/pages/CreateCampaignPage';
import HomePage from '@/pages/HomePage';

function App() {
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route
path="/create"
element={
<ProtectedRoute>
<CreateCampaignPage />
</ProtectedRoute>
}
/>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
);
}

const ProtectedRoute = ({ children }: { children: ReactNode }) => {
const { isConnected } = useAccount();

if (!isConnected) {
return <Navigate to="/" replace />;
}

return children;
};

export default App;
99 changes: 99 additions & 0 deletions campaign-launcher/client/src/components/AccountDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useState, type FC } from 'react';

import AccountCircleIcon from '@mui/icons-material/AccountCircle';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import LogoutIcon from '@mui/icons-material/Logout';
import {
Button,
List,
ListItemButton,
Popover,
Stack,
Typography,
} from '@mui/material';
import { useAccount, useDisconnect } from 'wagmi';

const formatAddress = (address?: string) => {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
};

const AccountDropdown: FC = () => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const { address } = useAccount();
const { disconnect } = useDisconnect();
const open = Boolean(anchorEl);

const closePopover = () => setAnchorEl(null);

const handleDisconnect = () => {
closePopover();
disconnect();
};

return (
<>
<Button
aria-describedby="account-popover"
onClick={(event) => setAnchorEl(event.currentTarget)}
sx={{
bgcolor: '#b9a6ff',
borderRadius: '4px',
color: '#1b0b50',
px: 1.25,
width: 'fit-content',
'&:hover': { bgcolor: '#a58fff' },
}}
>
<Stack direction="row" alignItems="center" gap={1}>
<AccountCircleIcon sx={{ width: 24, height: 24 }} />
<Typography sx={{ fontSize: 14, fontWeight: 700 }}>
{formatAddress(address)}
</Typography>
<KeyboardArrowDownIcon
sx={{
transform: open ? 'rotate(180deg)' : 'rotate(0deg)',
transition: 'transform 0.2s ease-in-out',
}}
/>
</Stack>
</Button>
<Popover
id="account-popover"
open={open}
onClose={closePopover}
anchorEl={anchorEl}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
slotProps={{
paper: {
elevation: 0,
sx: {
bgcolor: '#b9a6ff',
border: 'none',
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
color: '#1b0b50',
minWidth: anchorEl?.getBoundingClientRect().width,
},
},
}}
>
<List sx={{ p: 0 }}>
<ListItemButton
onClick={handleDisconnect}
sx={{
gap: 1,
fontWeight: 700,
'&:hover': { bgcolor: '#a58fff' },
}}
>
<LogoutIcon />
Sign out
</ListItemButton>
</List>
</Popover>
</>
);
};

export default AccountDropdown;
Loading
Loading