Skip to content

Commit 5f5eaaf

Browse files
committed
Migrate to Remix
1 parent 559f446 commit 5f5eaaf

24 files changed

+307
-135
lines changed

.gitignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ coverage.json
2323
.vercel
2424

2525
# Build Outputs
26-
.next/
27-
out/
2826
build
2927
dist
3028

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,22 @@ bun run dev
4747
The development server will be available at http://localhost:3000/.
4848

4949
The smart contracts are in the `contracts` directory, and the frontend is in the `apps` directory.
50+
51+
## How to deploy
52+
53+
### Build Settings
54+
55+
- **Repository:**
56+
[https://github.com/BlossomLabs/councilhaus](https://github.com/BlossomLabs/councilhaus)
57+
58+
- **Build Command:**
59+
```bash
60+
npm install -g bun && HUSKY=0 bun i && bun run --cwd apps/web/ build
61+
```
62+
63+
- **Publish Directory:**
64+
`apps/web/build`
65+
66+
### Environment Variables
67+
68+
- Use `VITE_WALLET_CONNECT_PROJECT_ID` for your wallet connect project ID.

apps/web/.gitignore

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@
99
# testing
1010
/coverage
1111

12-
# next.js
13-
/.next/
14-
/out/
15-
1612
# production
1713
/build
1814

@@ -33,4 +29,3 @@ yarn-error.log*
3329

3430
# typescript
3531
*.tsbuildinfo
36-
next-env.d.ts

apps/web/next.config.mjs

Lines changed: 0 additions & 12 deletions
This file was deleted.

apps/web/package.json

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,37 @@
11
{
22
"name": "web",
3-
"version": "1.0.0",
3+
"version": "1.1.0",
44
"private": true,
5+
"sideEffects": false,
6+
"type": "module",
57
"scripts": {
6-
"dev": "next dev --port 3000",
7-
"build": "next build",
8-
"start": "next start"
8+
"build": "remix vite:build",
9+
"dev": "remix vite:dev -l info"
910
},
1011
"dependencies": {
1112
"@rainbow-me/rainbowkit": "^2.1.5",
13+
"@remix-run/node": "^2.12.1",
14+
"@remix-run/react": "^2.12.1",
1215
"@repo/ui": "*",
1316
"@tanstack/react-query": "^5.55.4",
1417
"ethereum-blockies-base64": "^1.0.2",
1518
"graphql-request": "^7.1.0",
16-
"next": "^14.2.8",
1719
"react": "^18",
1820
"react-dom": "^18",
1921
"viem": "2.x",
2022
"wagmi": "^2.12.8"
2123
},
2224
"devDependencies": {
25+
"@remix-run/dev": "^2.12.1",
2326
"@repo/typescript-config": "*",
2427
"@types/node": "^20",
2528
"@types/react": "^18",
2629
"@types/react-dom": "^18",
2730
"autoprefixer": "^10",
2831
"postcss": "^8",
2932
"tailwindcss": "^3.4.10",
30-
"typescript": "^5"
33+
"typescript": "^5",
34+
"vite": "^5.1.0",
35+
"vite-tsconfig-paths": "^5.0.0"
3136
}
3237
}

apps/web/src/app/entry.client.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* By default, Remix will handle hydrating your app on the client for you.
3+
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
4+
* For more information, see https://remix.run/file-conventions/entry.client
5+
*/
6+
7+
import { RemixBrowser } from "@remix-run/react";
8+
import { StrictMode, startTransition } from "react";
9+
import { hydrateRoot } from "react-dom/client";
10+
11+
startTransition(() => {
12+
hydrateRoot(
13+
document,
14+
<StrictMode>
15+
<RemixBrowser />
16+
</StrictMode>,
17+
);
18+
});

apps/web/src/app/layout.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.

apps/web/src/app/root.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type { MetaFunction } from "@remix-run/node";
2+
import {
3+
Links,
4+
LiveReload,
5+
Meta,
6+
Outlet,
7+
Scripts,
8+
ScrollRestoration,
9+
} from "@remix-run/react";
10+
11+
import { ToastProvider } from "@repo/ui/components/ui/toast";
12+
import { Toaster } from "@repo/ui/components/ui/toaster";
13+
import { TooltipProvider } from "@repo/ui/components/ui/tooltip";
14+
import { Layout } from "../components/Layout";
15+
import { WalletProvider } from "../context/WalletProvider";
16+
17+
import {
18+
SITE_DESCRIPTION,
19+
SITE_EMOJI,
20+
SITE_NAME,
21+
SITE_URL,
22+
SOCIAL_TWITTER,
23+
} from "../../../../constants";
24+
25+
import "@repo/ui/globals.css";
26+
import "@rainbow-me/rainbowkit/styles.css";
27+
28+
export const meta: MetaFunction = () => [
29+
{
30+
charset: "utf-8",
31+
title: SITE_NAME,
32+
viewport: "width=device-width,initial-scale=1",
33+
},
34+
{ name: "description", content: SITE_DESCRIPTION },
35+
{ name: "image", content: SITE_EMOJI },
36+
{ name: "og:image", content: "/opengraph-image" },
37+
{ name: "og:title", content: SITE_NAME },
38+
{ name: "og:description", content: SITE_DESCRIPTION },
39+
{ name: "og:url", content: SITE_URL },
40+
{ name: "og:type", content: "website" },
41+
{ name: "og:site_name", content: SITE_NAME },
42+
{ name: "og:locale", content: "en_US" },
43+
{ name: "twitter:card", content: "summary_large_image" },
44+
{ name: "twitter:image", content: "/opengraph-image" },
45+
{ name: "twitter:title", content: SITE_NAME },
46+
{ name: "twitter:description", content: SITE_DESCRIPTION },
47+
{ name: "twitter:site", content: SOCIAL_TWITTER },
48+
{ name: "twitter:creator", content: SOCIAL_TWITTER },
49+
];
50+
51+
export default function App() {
52+
return (
53+
<html lang="en">
54+
<head>
55+
<Meta />
56+
<Links />
57+
</head>
58+
<body>
59+
<WalletProvider>
60+
<ToastProvider>
61+
<TooltipProvider>
62+
<Layout>
63+
<Outlet />
64+
</Layout>
65+
</TooltipProvider>
66+
<Toaster />
67+
</ToastProvider>
68+
</WalletProvider>
69+
<ScrollRestoration />
70+
<Scripts />
71+
</body>
72+
</html>
73+
);
74+
}

apps/web/src/app/routes/_index.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useEffect } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { DEFAULT_COUNCIL_ADDRESS, NETWORK } from "../../../../../constants";
4+
5+
export default function IndexPage() {
6+
const navigate = useNavigate();
7+
8+
useEffect(() => {
9+
navigate(`/c/${NETWORK}/${DEFAULT_COUNCIL_ADDRESS}`);
10+
}, [navigate]);
11+
12+
return null;
13+
}

apps/web/src/app/page.tsx renamed to apps/web/src/app/routes/c.$chain.$council.tsx

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,53 @@
11
"use client";
22

3+
import { useParams } from "@remix-run/react";
34
import { Badge } from "@repo/ui/components/ui/badge";
45
import { Label } from "@repo/ui/components/ui/label";
56
import { Skeleton } from "@repo/ui/components/ui/skeleton";
67
import { cn } from "@repo/ui/lib/utils";
7-
import Link from "next/link";
8-
import { useRouter } from "next/navigation";
9-
import { useEffect, useState } from "react";
8+
import { useEffect } from "react";
9+
import { useNavigate } from "react-router-dom";
1010
import { getAddress } from "viem";
1111
import { useAccount, useChains } from "wagmi";
12-
import { DEFAULT_COUNCIL_ADDRESS, NETWORK } from "../../../../constants";
13-
import { CouncilImage } from "../components/CouncilImage";
14-
import { CouncilName } from "../components/CouncilName";
15-
import VotingCard from "../components/VotingCard";
16-
import { useAllocation } from "../hooks/useAllocation";
17-
import { useCouncil } from "../hooks/useCouncil";
12+
import { NETWORK } from "../../../../../constants";
13+
import { CouncilImage } from "../../components/CouncilImage";
14+
import { CouncilName } from "../../components/CouncilName";
15+
import VotingCard from "../../components/VotingCard";
16+
import { useAllocation } from "../../hooks/useAllocation";
17+
import { useCouncil } from "../../hooks/useCouncil";
1818

19-
export default function Page() {
20-
const router = useRouter();
21-
const [council, setCouncil] = useState<`0x${string}` | undefined>(undefined);
19+
export default function CouncilPage() {
20+
const { chain, council } = useParams();
21+
const navigate = useNavigate();
22+
const normalizedAddress = council ? getAddress(council) : null;
2223

2324
useEffect(() => {
24-
// Ensure the code runs only on the client side
25-
if (!window.location.hash) {
26-
router.push(`#${DEFAULT_COUNCIL_ADDRESS}`);
25+
// Redirect if the chain is unsupported
26+
if (chain !== NETWORK) {
27+
navigate("/404");
28+
return;
2729
}
28-
// Set the council value once the hash is present
29-
const address = getAddress(
30-
window.location.hash?.slice(1) || DEFAULT_COUNCIL_ADDRESS,
31-
);
32-
setCouncil(address);
33-
}, [router]);
3430

31+
// Redirect if council address is invalid
32+
if (!normalizedAddress) {
33+
navigate("/404");
34+
return;
35+
}
36+
37+
// Redirect if the normalized address doesn't match the URL
38+
if (normalizedAddress !== council) {
39+
navigate(`/c/${chain}/${normalizedAddress}`, { replace: true });
40+
}
41+
}, [chain, council, normalizedAddress, navigate]);
42+
43+
if (!normalizedAddress) {
44+
return null;
45+
}
46+
47+
return <CouncilPageContent council={normalizedAddress} />;
48+
}
49+
50+
function CouncilPageContent({ council }: { council: `0x${string}` }) {
3551
// Fetch data when the council is available
3652
const { address } = useAccount();
3753
const {
@@ -52,16 +68,17 @@ export default function Page() {
5268

5369
return (
5470
<main>
55-
<Link
71+
<a
5672
href={`https://explorer.superfluid.finance/${NETWORK}-mainnet/accounts/${council}?tab=pools`}
5773
target="_blank"
74+
rel="noreferrer"
5875
>
5976
<CouncilImage image={councilImage} />
6077
<CouncilName
6178
name={councilName}
6279
className="min-h-12 text-4xl font-semibold tracking-wider text-accent mb-4 text-center"
6380
/>
64-
</Link>
81+
</a>
6582
<div className="flex flex-col gap-4 mt-4 mb-12 text-justify">
6683
{totalVotingPower ? (
6784
!address ? (
@@ -129,14 +146,22 @@ function ContractLinks({
129146
<div className={cn("flex flex-row gap-1 mb-4 items-center", className)}>
130147
<Label className="pr-2">Contracts: </Label>
131148
<Badge variant="outline">
132-
<Link href={`${explorer}/address/${council}`} target="_blank">
149+
<a
150+
href={`${explorer}/address/${council}`}
151+
target="_blank"
152+
rel="noreferrer"
153+
>
133154
Council
134-
</Link>
155+
</a>
135156
</Badge>
136157
<Badge variant="outline">
137-
<Link href={`${explorer}/address/${pool}`} target="_blank">
158+
<a
159+
href={`${explorer}/address/${pool}`}
160+
target="_blank"
161+
rel="noreferrer"
162+
>
138163
Pool
139-
</Link>
164+
</a>
140165
</Badge>
141166
</div>
142167
);

apps/web/src/components/AddressAvatar.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { cn } from "@repo/ui/lib/utils";
22
import makeBlockie from "ethereum-blockies-base64";
3-
import Image from "next/image";
43
import { useMemo } from "react";
54
import { isAddress } from "viem";
65
import { normalize } from "viem/ens";
@@ -56,7 +55,7 @@ function AddressAvatar({
5655
}, [ensAvatar, address, isLoading]);
5756

5857
return (
59-
<Image
58+
<img
6059
src={src}
6160
alt=""
6261
width={size}

0 commit comments

Comments
 (0)