Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6356353
feat: ens whitelabel governance
pikonha Apr 2, 2026
78d818c
feat: add theme dao-specific and update styles
brunod-e Apr 7, 2026
3edefc4
Merge branch 'dev' into codex/whitelabel-governance
brunod-e Apr 7, 2026
7bccd42
Merge branch 'dev' into codex/whitelabel-governance
brunod-e Apr 7, 2026
e7f00e1
feat: add dynamic brand color support to EnsAvatar component
brunod-e Apr 7, 2026
afc7959
feat: implement governance settings page and related components
brunod-e Apr 8, 2026
69f79a7
refactor: governance proposal components for whitelabel support
brunod-e Apr 9, 2026
fcea846
feat: update color variables and improve whitelabel support across co…
brunod-e Apr 9, 2026
fab8687
feat: enhance whitelabel support in proposal components and update la…
brunod-e Apr 9, 2026
8c1a542
Merge branch 'dev' into codex/whitelabel-governance
brunod-e Apr 13, 2026
b3f52d2
feat: update styling and improve whitelabel support in governance com…
brunod-e Apr 13, 2026
963b7c9
feat: enhance styling for PillTab and update WhitelabelShell and Whit…
brunod-e Apr 13, 2026
c27b765
feat: refactor whitelabel configuration and update logo handling in w…
brunod-e Apr 13, 2026
45e310c
feat: enhance governance components with search functionality and imp…
brunod-e Apr 13, 2026
9267440
feat: update styling and improve layout for governance and alert comp…
brunod-e Apr 13, 2026
79a2b97
Merge branch 'dev' into codex/whitelabel-governance
brunod-e Apr 13, 2026
eba7d83
fix: update layout and styling for Whitelabel components and improve …
brunod-e Apr 13, 2026
7fbc9bc
refactor: rename governance routes to proposals and update related co…
brunod-e Apr 13, 2026
69a736d
fix: adjust padding for ProposalHeader based on whitelabel status
brunod-e Apr 13, 2026
d9c5b6f
fix: update proposal links and improve whitelabel responsiveness acro…
brunod-e Apr 13, 2026
1b205bd
fix: improve layout and responsiveness for AlertsSection
brunod-e Apr 14, 2026
6c8f226
Merge branch 'dev' into codex/whitelabel-governance
brunod-e Apr 14, 2026
5546f10
fix: update custom domain and hostnames to staging environment for EN…
brunod-e Apr 14, 2026
e9e97bd
fix: remove custom domain from ENS configuration
brunod-e Apr 14, 2026
25e5a68
Merge branch 'codex/whitelabel-governance' of https://github.com/bloc…
brunod-e Apr 14, 2026
81aef81
feat: enhance RootLayout and CookieConsent for whitelabel support
brunod-e Apr 14, 2026
fde39fc
feat: implement debounced search input in WhitelabelHeader component
brunod-e Apr 14, 2026
295dc24
feat: update components for whitelabel support and improve voting mod…
brunod-e Apr 14, 2026
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
6 changes: 5 additions & 1 deletion apps/dashboard/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ NEXT_PUBLIC_BASE_URL=
NEXT_PUBLIC_API_TOKEN=
NEXT_PUBLIC_SITE_URL=

# local whitelabel override for dashboard development
# example: FORCE_DAO=AAVE then visit http://localhost:3000/governance
FORCE_DAO=

# social media links
NEXT_PUBLIC_ANTICAPTURE_TELEGRAM_BOT=https://t.me/<your-telegram-username>
NEXT_PUBLIC_ANTICAPTURE_SLACK_BOT=https://<your-slack-server>/slack/
Expand All @@ -14,4 +18,4 @@ RESEND_FROM_EMAIL=onboarding@resend.dev
CONTACT_EMAIL=

# GitHub token for higher API rate limits (5000 req/hour vs 60 unauthenticated)
GITHUB_TOKEN=
GITHUB_TOKEN=

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ export default async function OpengraphImage({

return await createDaoSectionOgImage({
daoId: daoIdEnum,
sectionTitle: "<governance>",
sectionTitle: "<proposals>",
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ import type { DaoIdEnum } from "@/shared/types/daos";

type Props = {
params: Promise<{ daoId: string; proposalId: string }>;
searchParams: Promise<{ proposalType?: string }>;
};

export async function generateMetadata(props: Props): Promise<Metadata> {
const params = await props.params;
const { proposalType } = await props.searchParams;
const daoId = params.daoId.toUpperCase() as DaoIdEnum;
const isOffchain = proposalType === "offchain";

const canonicalPath = `/${params.daoId}/governance/proposal/${params.proposalId}`;
const canonicalPath = isOffchain
? `/${params.daoId}/proposals/${params.proposalId}?proposalType=offchain`
: `/${params.daoId}/proposals/${params.proposalId}`;

return {
title: `${daoId} DAO Governance Proposal | Security Analysis — Anticapture`,
Expand All @@ -30,10 +35,12 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
};
}

export default function ProposalPage() {
export default async function ProposalPage({ searchParams }: Props) {
const { proposalType } = await searchParams;

return (
<div>
<ProposalSection />
<ProposalSection isOffchain={proposalType === "offchain"} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createDaoSectionOgImage } from "@/shared/og";
import type { DaoIdEnum } from "@/shared/types/daos";

export const alt = "Anticapture Governance";
export const alt = "Anticapture Proposals";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

Expand All @@ -15,6 +15,6 @@ export default async function OpengraphImage({

return await createDaoSectionOgImage({
daoId: daoIdEnum,
sectionTitle: "<governance>",
sectionTitle: "<proposals>",
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
const params = await props.params;
const daoId = params.daoId.toUpperCase() as DaoIdEnum;

const canonicalPath = `/${params.daoId}/governance`;
const canonicalPath = `/${params.daoId}/proposals`;

return {
title: `${daoId} DAO Governance Proposals | Security Analysis — Anticapture`,
Expand All @@ -30,7 +30,7 @@ export async function generateMetadata(props: Props): Promise<Metadata> {
};
}

export default function GovernancePage() {
export default function ProposalsPage() {
return (
<div>
<GovernanceSection />
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/app/alerts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default function DonatePage() {
<HeaderMobile className="fixed! top-0" />
</div>
<div className="flex min-h-screen w-full flex-col items-center">
<div className="w-full flex-1">
<div className="mt-14 w-full flex-1 lg:mt-0">
<AlertsSection />
</div>
<Footer />
Expand Down
9 changes: 7 additions & 2 deletions apps/dashboard/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@
--color-surface-default: var(--base-primary-foreground);
--color-surface-contrast: var(--base-muted);
--color-surface-hover: var(--base-border);
--color-surface-action: var(--base-primary);
--color-surface-action-hover: var(--base-primary-opacity-darker);
--color-surface-action: var(--base-brand);
--color-surface-action-hover: color-mix(
in srgb,
var(--base-brand) 80%,
black
);
--color-surface-destructive: var(--base-destructive);
--color-surface-destructive-hover: var(--base-destructive-hover);
--color-surface-disabled: var(--base-muted);
Expand Down Expand Up @@ -186,6 +190,7 @@

/* root colors */
--radius: 0.625rem;
--radius-base: 0px;
--base-background: #ffffff;
--base-foreground: #09090b;
--base-muted: #f4f4f5;
Expand Down
33 changes: 28 additions & 5 deletions apps/dashboard/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "@/app/globals.css";
import "tailwindcss";
import type { Metadata } from "next";
import { Inter, Roboto_Mono } from "next/font/google";
import { headers } from "next/headers";
import type { ReactNode } from "react";
import { Toaster } from "react-hot-toast";

Expand All @@ -10,6 +11,7 @@ import { HelpPopover } from "@/shared/components";
import { GlobalProviders } from "@/shared/providers/GlobalProviders";
import ConditionalPostHog from "@/shared/services/posthog/ConditionalPostHog";
import UmamiScript from "@/shared/services/umami";
import { resolveDaoIdFromHostname } from "@/shared/utils/whitelabel";

const inter = Inter({ weight: ["400", "500", "600"], subsets: ["latin"] });

Expand Down Expand Up @@ -55,26 +57,47 @@ export const metadata: Metadata = {
},
};

export default function RootLayout({ children }: { children: ReactNode }) {
export default async function RootLayout({
children,
}: {
children: ReactNode;
}) {
const headersList = await headers();
const host =
headersList.get("x-forwarded-host") ?? headersList.get("host") ?? "";
const hostname = host.split(":")[0];
const isWhitelabel = !!resolveDaoIdFromHostname(hostname);

return (
<html lang="en" className="dark">
<html
lang="en"
className={isWhitelabel ? undefined : "dark"}
suppressHydrationWarning
>
<head>
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover"
/>
{!isWhitelabel && (
<script
dangerouslySetInnerHTML={{
__html: `if(location.pathname.startsWith('/whitelabel/'))document.documentElement.classList.remove('dark')`,
}}
/>
)}
</head>
<body
className={`${inter.className} ${roboto.variable} bg-surface-background`}
>
<div
data-vaul-drawer-wrapper=""
className="border-light-dark mx-auto max-w-screen-2xl overflow-x-hidden border xl:overflow-hidden"
className="border-border-default mx-auto max-w-screen-2xl overflow-x-hidden border xl:overflow-hidden"
>
<GlobalProviders>
{children}
<CookieConsent />
<HelpPopover />
<CookieConsent isWhitelabel={isWhitelabel} />
<HelpPopover isWhitelabel={isWhitelabel} />
</GlobalProviders>
<Toaster position="bottom-left" reverseOrder={false} />
<ConditionalPostHog />
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/app/sitemap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const STATIC_ROUTES = [
const DAO_SUB_ROUTES = [
"",
"/risk-analysis",
"/governance",
"/proposals",
"/token-distribution",
"/holders-and-delegates",
"/activity-feed",
Expand Down
24 changes: 24 additions & 0 deletions apps/dashboard/app/whitelabel/[daoId]/activity-feed/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Metadata } from "next";

import { ActivityFeedSection } from "@/features/feed";
import daoConfigByDaoId from "@/shared/dao-config";
import type { DaoIdEnum } from "@/shared/types/daos";

type Props = {
params: Promise<{ daoId: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { daoId } = await params;
const daoIdEnum = daoId.toUpperCase() as DaoIdEnum;
const daoConfig = daoConfigByDaoId[daoIdEnum];

return {
title: "Activity Feed",
description: `Monitor governance activity and participation signals for ${daoConfig.name}.`,
};
}

export default function WhitelabelActivityFeedPage() {
return <ActivityFeedSection />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Metadata } from "next";

import { WhitelabelDelegateDetailPage } from "@/features/holders-and-delegates/components/WhitelabelDelegateDetailPage";
import daoConfigByDaoId from "@/shared/dao-config";
import type { DaoIdEnum } from "@/shared/types/daos";

type Props = {
params: Promise<{ daoId: string; address: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { daoId } = await params;
const daoIdEnum = daoId.toUpperCase() as DaoIdEnum;
const daoConfig = daoConfigByDaoId[daoIdEnum];

return {
title: "Delegate details",
description: `Inspect delegate voting behavior and history for ${daoConfig.name}.`,
};
}

export default async function WhitelabelDelegateAddressPage({ params }: Props) {
const { daoId, address } = await params;

return (
<WhitelabelDelegateDetailPage
daoId={daoId.toUpperCase() as DaoIdEnum}
address={address}
/>
);
}
27 changes: 27 additions & 0 deletions apps/dashboard/app/whitelabel/[daoId]/delegates/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Metadata } from "next";

import { HoldersAndDelegatesSection } from "@/features/holders-and-delegates";
import daoConfigByDaoId from "@/shared/dao-config";
import type { DaoIdEnum } from "@/shared/types/daos";

type Props = {
params: Promise<{ daoId: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { daoId } = await params;
const daoIdEnum = daoId.toUpperCase() as DaoIdEnum;
const daoConfig = daoConfigByDaoId[daoIdEnum];

return {
title: "Delegates",
description: `Explore delegate activity and holder concentration for ${daoConfig.name}.`,
};
}

export default async function WhitelabelDelegatesPage({ params }: Props) {
const { daoId } = await params;
const daoIdEnum = daoId.toUpperCase() as DaoIdEnum;

return <HoldersAndDelegatesSection daoId={daoIdEnum} />;
Comment thread
brunod-e marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Metadata } from "next";

import { GovernanceSettingsSection } from "@/features/governance-settings/GovernanceSettingsSection";
import daoConfigByDaoId from "@/shared/dao-config";
import type { DaoIdEnum } from "@/shared/types/daos";

type Props = {
params: Promise<{ daoId: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { daoId } = await params;
const daoIdEnum = daoId.toUpperCase() as DaoIdEnum;
const daoConfig = daoConfigByDaoId[daoIdEnum];

return {
title: "Governance Settings",
description: `View the contracts and parameters that govern how ${daoConfig.name} operates.`,
};
}

export default function WhitelabelGovernanceSettingsPage() {
return <GovernanceSettingsSection />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import type { Metadata } from "next";

import { HoldersAndDelegatesSection } from "@/features/holders-and-delegates";
import daoConfigByDaoId from "@/shared/dao-config";
import type { DaoIdEnum } from "@/shared/types/daos";

type Props = {
params: Promise<{ daoId: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { daoId } = await params;
const daoIdEnum = daoId.toUpperCase() as DaoIdEnum;
const daoConfig = daoConfigByDaoId[daoIdEnum];

return {
title: "Holders & Delegates",
description: `Explore holder concentration and delegate activity for ${daoConfig.name}.`,
};
}

export default async function WhitelabelHoldersAndDelegatesPage({
params,
}: Props) {
const { daoId } = await params;
const daoIdEnum = daoId.toUpperCase() as DaoIdEnum;

return <HoldersAndDelegatesSection daoId={daoIdEnum} />;
}
Loading
Loading