Skip to content
Merged
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
31 changes: 12 additions & 19 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,9 @@ name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"

jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'

runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -36,12 +24,17 @@ jobs:
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
plugins: 'code-review@claude-code-plugins'
prompt: '/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
claude_args: '--allowedTools mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr review:*),Bash(gh pr view:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*),Read,Glob,Grep'
display_report: 'true'
use_sticky_comment: 'true'
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://code.claude.com/docs/en/cli-reference for available options
claude_args: '--allowedTools Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr review:*),Bash(gh pr view:*),Bash(git diff:*),Bash(git log:*),Bash(git show:*),Read,Glob,Grep'
prompt: |
Review the code changes in PR #${{ github.event.pull_request.number }} of this repository.

Steps:
1. Run `gh pr diff ${{ github.event.pull_request.number }}` to see the changes
2. Run `gh pr view ${{ github.event.pull_request.number }}` to see the PR description
3. Review the changes for bugs, security issues, and code quality problems
4. Post your review using:
`gh pr review ${{ github.event.pull_request.number }} --comment --body "YOUR_REVIEW"`

Always post a review comment, even if the code looks good (write "LGTM" with a brief summary in that case).
Focus on real issues — not style nitpicks.
27 changes: 0 additions & 27 deletions governance-app/app/delegate/page.tsx

This file was deleted.

44 changes: 30 additions & 14 deletions governance-app/app/delegates/page.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,52 @@
// ABOUTME: Delegates page — personal delegation form (wallet-connected) and
// the full delegate leaderboard ordered by voting power.
import { ProposalErrorState } from '~/components/proposals/ProposalErrorState'
import { DelegateLeaderboardRow } from '~/components/delegates/DelegateLeaderboardRow'
import { getDelegateOverview } from '~/lib/governance/delegates'
import {
DelegateLeaderboardRow,
type DelegateRowData,
} from '~/components/delegates/DelegateLeaderboardRow'
import { DelegateFormSection } from '~/components/delegates/DelegateFormSection'
import {
getDelegateOverview,
formatDelegatedShare,
} from '~/lib/governance/delegates'
import { formatTokenAmount } from '~/lib/governance/format'

export const dynamic = 'force-dynamic'

export default async function DelegatesPage() {
try {
const overview = await getDelegateOverview()

const rows: DelegateRowData[] = overview.delegates.map((d, index) => ({
address: d.address,
rank: index + 1,
votingPower: formatTokenAmount(d.votingPower),
tokenBalance: formatTokenAmount(d.tokenBalance),
delegatorCount: d.delegatorCount,
delegatedShare: formatDelegatedShare(d.votingPower, overview.totalSupply),
}))

return (
<section className="space-y-8">
<DelegateFormSection />

<div className="rounded-[2rem] border border-brand-ui-primary/10 bg-white p-8 shadow-sm">
<p className="text-xs font-semibold uppercase tracking-[0.24em] text-brand-ui-primary/55">
Delegation Read Path
Delegate Leaderboard
</p>
<h2 className="mt-4 text-4xl font-semibold text-brand-ui-primary">
Delegate leaderboard
<h2 className="mt-4 text-3xl font-semibold text-brand-ui-primary">
Top delegates by voting power
</h2>
<p className="mt-4 max-w-3xl text-base leading-7 text-brand-ui-primary/72">
Current delegation relationships reconstructed from on-chain
`DelegateChanged` events and hydrated with live voting power from
the UP token contract.
DelegateChanged events and hydrated with live voting power from the
UP token contract.
</p>
</div>
<div className="grid gap-4">
{overview.delegates.map((delegate, index) => (
<DelegateLeaderboardRow
key={delegate.address}
delegate={delegate}
rank={index + 1}
totalSupply={overview.totalSupply}
/>
{rows.map((row) => (
<DelegateLeaderboardRow key={row.address} {...row} />
))}
</div>
</section>
Expand Down
21 changes: 12 additions & 9 deletions governance-app/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,27 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ToastProvider, UnlockUIProvider } from '@unlock-protocol/ui'
import { useState } from 'react'
import { governanceEnv } from '~/config/env'
import { ConnectModalProvider } from '~/hooks/useConnectModal'

type ProviderProps = {
children: React.ReactNode
}

function WalletProvider({ children }: ProviderProps) {
if (!governanceEnv.privyAppId) {
return <>{children}</>
}

return (
<PrivyProvider
appId={governanceEnv.privyAppId}
config={{
loginMethods: ['wallet', 'email', 'google'],
loginMethods: ['wallet', 'email', 'google', 'farcaster'],
embeddedWallets: {
createOnLogin: 'off',
},
appearance: {
landingHeader: 'Unlock DAO Governance',
landingHeader: '',
},
// @ts-expect-error internal api
_render: {
standalone: true,
},
}}
>
Expand All @@ -51,9 +52,11 @@ export function Providers({ children }: ProviderProps) {
return (
<UnlockUIProvider Link={Link}>
<WalletProvider>
<QueryClientProvider client={queryClient}>
<ToastProvider>{children}</ToastProvider>
</QueryClientProvider>
<ConnectModalProvider>
<QueryClientProvider client={queryClient}>
<ToastProvider>{children}</ToastProvider>
</QueryClientProvider>
</ConnectModalProvider>
</WalletProvider>
</UnlockUIProvider>
)
Expand Down
8 changes: 8 additions & 0 deletions governance-app/public/images/unlock-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions governance-app/src/components/ConnectModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// ABOUTME: Renders the Privy LoginModal inside an Unlock UI Modal wrapper.
// Visibility is controlled by ConnectModalProvider via useConnectModal.
'use client'

import { Modal } from '@unlock-protocol/ui'
import { LoginModal } from '@privy-io/react-auth'
import { useConnectModal } from '~/hooks/useConnectModal'

export function ConnectModal() {
const { open, closeConnectModal } = useConnectModal()

return (
<Modal isOpen={open} setIsOpen={closeConnectModal} size="small">
<div className="w-full max-w-sm rounded-2xl bg-white">
<LoginModal open={open} />
</div>
</Modal>
)
}
40 changes: 40 additions & 0 deletions governance-app/src/components/TermsOfServiceModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// ABOUTME: Shows a one-time terms of service acceptance modal on first visit.
// Acceptance is persisted in localStorage so the modal only appears once.
'use client'

import { Button, Modal } from '@unlock-protocol/ui'
import { useTermsOfService } from '~/hooks/useTermsOfService'

export function TermsOfServiceModal() {
const { termsAccepted, saveTermsAccepted, termsLoading } = useTermsOfService()
const showTermsModal = !termsLoading && !termsAccepted

return (
<Modal isOpen={showTermsModal} setIsOpen={saveTermsAccepted}>
<div className="flex flex-col justify-center gap-4 bg-white">
<span className="text-base">
No account required ✨, but you need to agree to our{' '}
<a
className="text-brand-ui-primary outline-none"
href="https://unlock-protocol.com/terms"
target="_blank"
rel="noopener noreferrer"
>
Terms of Service
</a>{' '}
and{' '}
<a
className="text-brand-ui-primary outline-none"
href="https://unlock-protocol.com/privacy"
target="_blank"
rel="noopener noreferrer"
>
Privacy Policy
</a>
.
</span>
<Button onClick={saveTermsAccepted}>I agree</Button>
</div>
</Modal>
)
}
Loading
Loading