Showing preview only (259K chars total). Download the full file or copy to clipboard to get everything.
Repository: ProofOfEstimate/uefa-poe
Branch: main
Commit: c5c6e7817568
Files: 75
Total size: 240.2 KB
Directory structure:
gitextract_cllvgxn0/
├── .eslintrc.json
├── .gitignore
├── README.md
├── app/
│ ├── layout.tsx
│ ├── leaderboard/
│ │ └── page.tsx
│ ├── match/
│ │ └── [id]/
│ │ └── page.tsx
│ ├── page.tsx
│ └── providers.tsx
├── components/
│ ├── TableRow.tsx
│ ├── connect-wallet-button.tsx
│ ├── dark-mode-toggle.tsx
│ ├── footer.tsx
│ ├── market-stats.tsx
│ ├── match-card.tsx
│ ├── match-day.tsx
│ ├── nav-bar.tsx
│ ├── quick-tour-dialog.tsx
│ ├── sidenav.tsx
│ └── ui/
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── carousel.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── separator.tsx
│ ├── skeleton.tsx
│ ├── slider.tsx
│ ├── table.tsx
│ ├── tabs.tsx
│ ├── theme-provider.tsx
│ ├── toast.tsx
│ ├── toaster.tsx
│ ├── tooltip.tsx
│ └── use-toast.ts
├── components.json
├── contexts/
│ ├── AutoConnectProvider.tsx
│ ├── ContextProvider.tsx
│ └── NetworkConfigurationProvider.tsx
├── errors/
│ ├── NoUserAccountError.ts
│ └── WalletNotConnectedError.ts
├── hooks/
│ ├── mutations/
│ │ ├── useAirdropSol.tsx
│ │ ├── useCollectPoints.ts
│ │ ├── useMakeEstimate.tsx
│ │ ├── useRegisterUser.tsx
│ │ └── useUpdateEstimate.ts
│ ├── queries/
│ │ ├── useAllPolls.ts
│ │ ├── useAllPollsByUser.ts
│ │ ├── useAllUserAccounts.ts
│ │ ├── useAllUserPredictions.ts
│ │ ├── useEstimateUpdatesByPoll.ts
│ │ ├── usePollById.ts
│ │ ├── useUserAccount.ts
│ │ ├── useUserBonkBalance.ts
│ │ ├── useUserEstimateByPoll.ts
│ │ ├── useUserScore.ts
│ │ └── useUserSolBalance.ts
│ ├── states/
│ │ └── useTabStore.tsx
│ ├── useAnchorProgram.tsx
│ └── useIntersectionObserver.tsx
├── idl/
│ ├── poe.json
│ └── poe.ts
├── lib/
│ ├── dummyData.ts
│ ├── types.ts
│ └── utils.ts
├── next.config.js
├── next.config.mjs
├── package.json
├── postcss.config.mjs
├── styles/
│ └── globals.css
├── tailwind.config.ts
├── texts/
│ └── toastTitles.ts
├── tsconfig.json
└── utils/
└── sendVersionedTransaction.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.json
================================================
{
"extends": "next/core-web-vitals"
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
================================================
FILE: README.md
================================================
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
================================================
FILE: app/layout.tsx
================================================
import "@/styles/globals.css";
import { Inter as FontSans } from "next/font/google";
import { cn } from "@/lib/utils";
import { Separator } from "@/components/ui/separator";
import Providers from "./providers";
import { NavBar } from "@/components/nav-bar";
import { Footer } from "@/components/footer";
const fontSans = FontSans({
subsets: ["latin"],
variable: "--font-sans",
});
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<head />
<body
className={cn(
"flex flex-col min-h-screen bg-background font-sans antialiased",
fontSans.variable
)}
>
<Providers>
<NavBar />
<Separator />
{children}
</Providers>
<Footer />
</body>
</html>
);
}
================================================
FILE: app/leaderboard/page.tsx
================================================
"use client";
import TableRow from "@/components/TableRow";
import { Skeleton } from "@/components/ui/skeleton";
import { useAllUserAccounts } from "@/hooks/queries/useAllUserAccounts";
import useAnchorProgram from "@/hooks/useAnchorProgram";
import { useWallet } from "@solana/wallet-adapter-react";
import React from "react";
const Leaderboard = () => {
const program = useAnchorProgram();
const { publicKey } = useWallet();
const { data: userScores, isLoading: isScoresLoading } =
useAllUserAccounts(program);
const rank = userScores?.findIndex(
(element) =>
element.account.userAddress.toBase58() === publicKey?.toBase58()
);
const scores = userScores?.map((account, i) => {
return {
number: i + 1,
name:
account.account.userAddress.toBase58().slice(0, 4) +
"..." +
account.account.userAddress.toBase58().slice(-4),
points: account.account.score.toFixed(2),
isGold: i == 0,
highlight: rank === i,
};
});
return (
<div className="h-full w-full min-h-screen flex items-center justify-center">
<main className="w-[40rem] bg-white shadow-[0px_5px_15px_8px_#e4e7fb] flex flex-col items-center rounded-lg">
<div
id="header"
className="w-full flex items-center justify-between p-10"
>
<h1 className="font-rubik text-[1.7rem] text-[#141a39] uppercase cursor-default">
Ranking
</h1>
</div>
<div id="leaderboard" className="w-full relative">
<div className="w-full h-[5.5rem] bg-[#5c5be5] absolute top-[-0.5rem] shadow-[0px_15px_11px_-6px_#7a7a7d]">
<div className="absolute bottom-[-0.8rem] left-[0.35rem] w-[1.5rem] h-[1.5rem] bg-[#5c5be5] rotate-45 z-[-1]"></div>
<div className="absolute bottom-[-0.8rem] right-[0.35rem] w-[1.5rem] h-[1.5rem] bg-[#5c5be5] rotate-45 z-[-1]"></div>
</div>
{scores == undefined ? (
<Skeleton />
) : (
<table className="w-full border-collapse table-fixed text-[#141a39] cursor-default mb-20">
{scores.map((row, index) => (
<TableRow key={index} {...row} />
))}
</table>
)}
</div>
</main>
</div>
);
};
export default Leaderboard;
================================================
FILE: app/match/[id]/page.tsx
================================================
"use client";
import { Button } from "@/components/ui/button";
import useAnchorProgram from "@/hooks/useAnchorProgram";
import { allMatches } from "@/lib/dummyData";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { useRouter } from "next/navigation";
import { FaArrowLeftLong } from "react-icons/fa6";
import Image from "next/image";
import { Slider } from "@/components/ui/slider";
import {
Area,
Brush,
CartesianGrid,
ComposedChart,
Line,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import { useEffect, useState } from "react";
import { usePollById } from "@/hooks/queries/usePollById";
import { Skeleton } from "@/components/ui/skeleton";
import { useUserEstimateByPoll } from "@/hooks/queries/useUserEstimateByPoll";
import clsx from "clsx";
import { TbLoader2 } from "react-icons/tb";
import { useMakeEstimate } from "@/hooks/mutations/useMakeEstimate";
import { useUpdateEstimate } from "@/hooks/mutations/useUpdateEstimate";
import { useCollectPoints } from "@/hooks/mutations/useCollectPoints";
import { useUserScore } from "@/hooks/queries/useUserScore";
import MarketStats from "@/components/market-stats";
import { useEstimateUpdatesByPoll } from "@/hooks/queries/useEstimateUpdatesByPoll";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
const Match = ({ params }: { params: { id: string } }) => {
const router = useRouter();
const program = useAnchorProgram();
const { connection } = useConnection();
const wallet = useWallet();
const matchId = Number.parseInt(params.id) - 1;
const match = allMatches[matchId];
const { data: poll, isLoading: isLoadingPoll } = usePollById(
program,
matchId,
true
);
const { data: estimateUpdates } = useEstimateUpdatesByPoll(
program,
matchId,
wallet.publicKey
);
const [brushStartIndex, setBrushStartIndex] = useState<number>();
const [brushEndIndex, setBrushEndIndex] = useState<number>();
const handleBrushChange = ({
startIndex,
endIndex,
}: {
startIndex?: number;
endIndex?: number;
}) => {
setBrushStartIndex(startIndex);
setBrushEndIndex(endIndex);
};
const {
data: userEstimate,
isError: isErrorEstimate,
error: errorEstimate,
isLoading: isLoadingEstimate,
} = useUserEstimateByPoll(
program,
connection,
wallet.publicKey,
matchId,
true
);
const [estimate, setEstimate] = useState(
userEstimate !== null && userEstimate !== undefined
? (userEstimate.lowerEstimate + userEstimate.upperEstimate) / 2
: undefined
);
const { mutate: submitEstimate, isPending: isSubmitting } = useMakeEstimate(
program,
connection,
wallet
);
const { mutate: updateEstimate, isPending: isUpdating } = useUpdateEstimate(
program,
connection,
wallet
);
const { mutate: collectPoints, isPending: isCollecting } = useCollectPoints(
program,
connection,
wallet
);
const { data: userScore, isLoading: isLoadingScore } = useUserScore(
program,
connection,
wallet.publicKey,
matchId,
true
);
useEffect(() => {
if (userEstimate !== null && userEstimate !== undefined) {
setEstimate(
(userEstimate.lowerEstimate + userEstimate.upperEstimate) / 2
);
}
}, [userEstimate]);
const handleChange = (estimate: [number]) => {
setEstimate(estimate[0]);
};
return (
<main className="flex min-h-screen flex-col justify-start items-start px-4 sm:px-12 lg:px-16 py-4 sm:py-8 w-full">
<Button
onClick={() => router.back()}
variant={"ghost"}
className="p-0 hover:bg-transparent"
>
<FaArrowLeftLong />
</Button>
<div className="flex flex-col md:flex-row w-full gap-4">
<div className="basis-2/3">
<div className="text-lg font-medium mt-8">{match.date}</div>
<div className="flex flex-col items-start gap-4 mt-2">
<div className="flex items-center gap-4 w-60">
<Image
width={36}
height={27}
alt="Flag of team A"
src={
match.logoA ? match.logoA : "https://via.placeholder.com/50"
}
/>
<div className="text-xl font-bold">{match.teamA}</div>
<span className="text-lg font-bold ml-auto">{match.resultA}</span>
</div>
<div className="flex items-center gap-4 w-60">
<Image
width={36}
height={27}
alt="Flag of team B"
src={
match.logoB ? match.logoB : "https://via.placeholder.com/50"
}
/>
<div className="text-xl font-bold">{match.teamB}</div>
<span className="text-lg font-bold ml-auto">{match.resultB}</span>
</div>
</div>
<div className="text-2xl font-bold mt-8">{`Will ${match.teamA} win against ${match.teamB}?`}</div>
{/* {poll ? (
<div className="text-2xl font-bold mt-8">{poll?.question}</div>
) : (
<Skeleton />
)} */}
<div className="flex w-40 justify-between">
<p className="block text-sm">Market Prediction:</p>
<p className="text-sm font-bold">
{poll && poll.collectiveEstimate !== null
? (poll.collectiveEstimate / 10000).toFixed(0) + "%"
: "-"}
</p>
</div>
<div className="flex w-40 justify-between">
<p className="block text-sm">Your Prediction:</p>
<p
className={clsx(
"text-sm font-bold",
estimate !== userEstimate?.lowerEstimate
? "dark:text-yellow-300"
: ""
)}
>
{estimate !== undefined ? estimate + "%" : "-"}
</p>
</div>
<div className="flex w-5/6 sm:w-1/2 md:w-2/3 gap-4 items-center my-4">
<Slider
className="hover:cursor-pointer"
onValueChange={handleChange}
value={[estimate !== undefined ? estimate : 50]}
min={0}
max={100}
step={1}
disabled={poll?.result !== null}
/>
<Button
variant={"secondary"}
disabled={poll?.result !== null}
size={"sm"}
onClick={() => {
if (userEstimate !== null && userEstimate !== undefined) {
setEstimate(
(userEstimate.lowerEstimate + userEstimate.upperEstimate) /
2
);
} else {
setEstimate(undefined);
}
}}
>
Reset
</Button>
</div>
{isLoadingPoll ? (
<Skeleton className="w-40 h-9 rounded-md" />
) : poll?.result !== null ? (
userScore === null || userScore === undefined || isLoadingScore ? (
<div>
{poll !== undefined ? (
poll.result ? (
<div className="text-primary font-bold">
{match.teamA} won!
</div>
) : (
<div className="text-primary font-bold">
{match.teamA} did not win!
</div>
)
) : (
""
)}
</div>
) : (
<Button
disabled={isCollecting}
className="w-fit"
onClick={() => collectPoints({ pollId: matchId })}
>
{isCollecting && (
<TbLoader2 className="mr-2 h-4 w-4 animate-spin" />
)}
Withdraw your funds
</Button>
)
) : userEstimate !== undefined && userEstimate !== null ? (
<Button
disabled={
isUpdating ||
(estimate === userEstimate.lowerEstimate &&
estimate === userEstimate.upperEstimate)
}
className="w-fit"
onClick={() =>
updateEstimate({
pollId: matchId,
lowerEstimate: estimate,
upperEstimate: estimate,
})
}
>
{isUpdating && (
<TbLoader2 className="mr-2 h-4 w-4 animate-spin" />
)}
Update Estimate
</Button>
) : (
<div className="flex gap-4">
<Button
disabled={isSubmitting || estimate === undefined}
className="font-bold rounded w-fit"
onClick={() =>
submitEstimate({
pollId: matchId,
lowerEstimate: estimate,
upperEstimate: estimate,
})
}
>
{isSubmitting && (
<TbLoader2 className="mr-2 h-4 w-4 animate-spin" />
)}
Bet 100 BONK
</Button>
<Avatar>
<AvatarImage src={"/bonk_poe.png"} />
</Avatar>
</div>
)}
</div>
<MarketStats
matchId={matchId}
profitScore={
poll?.result !== null &&
userEstimate !== null &&
userEstimate !== undefined &&
userEstimate.payoutScore
? ((userEstimate.payoutScore - 1) * 100).toFixed(2)
: ""
}
reputationScore={
poll?.result !== null &&
userEstimate !== null &&
userEstimate !== undefined &&
userEstimate.reputationScore
? userEstimate.reputationScore?.toFixed(2)
: ""
}
/>
</div>
<div className="w-full border rounded-lg p-8 mt-8">
<ResponsiveContainer width="100%" height={400}>
<ComposedChart data={estimateUpdates}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis
dataKey="name"
type="number"
domain={[]}
tickFormatter={(number) =>
new Date(number * 1000).toLocaleString()
}
tickCount={10}
/>
<YAxis />
<Tooltip
contentStyle={{
backgroundColor: "hsl(var(--primary))",
}}
itemStyle={{ color: "hsl(var(--primary-foreground))" }}
labelStyle={{ color: "hsl(var(--primary-foreground))" }}
formatter={(value, name, prop) => {
switch (name) {
case "confidenceInterval":
return [undefined, undefined];
case "estimate":
const interval =
prop.payload.confidenceInterval[1] -
prop.payload.confidenceInterval[0];
return [
Number(value).toFixed(2) +
"% ± " +
(interval / 2).toFixed(1) +
"%",
"Market Prediction",
];
default:
return [undefined, undefined];
}
}}
labelFormatter={(label) =>
new Date(label * 1000).toLocaleString()
}
/>
<Area
type="monotone"
dataKey="confidenceInterval"
fill="hsl(var(--primary))"
opacity={0.2}
stroke="#ffffff00"
activeDot={false}
isAnimationActive={false}
/>
<Line
dot={false}
type="linear"
dataKey="estimate"
stroke="hsl(var(--primary))"
isAnimationActive={false}
/>
<Brush
dataKey="name"
height={30}
stroke="#8884d8"
onChange={handleBrushChange}
startIndex={brushStartIndex}
endIndex={brushEndIndex}
tickFormatter={(number) =>
new Date(number * 1000).toLocaleString()
}
/>
</ComposedChart>
</ResponsiveContainer>
</div>
<div className="mt-16 text-xl font-bold">Details</div>
<div className="pt-4 pb-16 text-lg w-full sm:w-5/6 md:w-1/2">
This market will resolve to true if {match.teamA} wins against{" "}
{match.teamB}. In any other case, e.g. a draw, {match.teamB} wins or the
game is canceled, this market will resolve to false.
</div>
</main>
);
};
export default Match;
================================================
FILE: app/page.tsx
================================================
"use client";
import { MatchCard } from "@/components/match-card";
import { MatchDay } from "@/components/match-day";
import QuickTourDialog from "@/components/quick-tour-dialog";
import SideNav from "@/components/sidenav";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useRegisterUser } from "@/hooks/mutations/useRegisterUser";
import { useAllPolls } from "@/hooks/queries/useAllPolls";
import { useUserAccount } from "@/hooks/queries/useUserAccount";
import { useTabsStore } from "@/hooks/states/useTabStore";
import useAnchorProgram from "@/hooks/useAnchorProgram";
import {
matchesFirstMatchDay,
matchesSecondMatchday,
matchesThirdMatchday,
matchesRound16,
matchesQuarterFinals,
matchesSemiFinals,
matchesFinal,
allMatches,
} from "@/lib/dummyData";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { useEffect, useState } from "react";
import { FaChevronUp } from "react-icons/fa6";
export default function App() {
const program = useAnchorProgram();
const { connection } = useConnection();
const wallet = useWallet();
const { data: userAccount, isLoading: isScoreLoading } = useUserAccount(
program,
connection,
wallet.publicKey
);
const { data: allPolls, isLoading: isAllPollsLoading } = useAllPolls(program);
// const nextMarketIndex = allPolls?.filter((p) => {
// return p.result !== null;
// }).length;
// const comingMatches = allMatches.filter((match) => {
// const index = nextMarketIndex ?? 0;
// return Number.parseInt(match.id) - 1 > index;
// });
const [isVisible, setIsVisible] = useState(false);
const tab = useTabsStore((state) => state.tab);
const setTab = useTabsStore((state) => state.setTab);
useEffect(() => {
const handleScroll = () => {
setIsVisible(window.scrollY > 1000);
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
const { mutate: registerUser } = useRegisterUser(program, connection, wallet);
return (
<div className="flex">
<aside className="hidden md:block md:min-w-20 md:w-1/5">
<SideNav />
</aside>
<main className="flex w-full min-h-screen flex-col items-center justify-between p-4 sm:p-12">
<div className="block sm:hidden mb-2">
<QuickTourDialog />
{wallet.publicKey && userAccount === null && !isScoreLoading && (
<Button className="sm:hidden ml-4" onClick={() => registerUser()}>
Mint BONK
</Button>
)}
</div>
{/* <div className="text-4xl font-bold mb-4">Next Match</div> */}
{/* {nextMarketIndex ? (
<MatchCard match={allMatches[nextMarketIndex]} />
) : (
<Skeleton className="h-[350px] w-[400px] rounded-xl" />
)} */}
<AllMatches />
{/* <Tabs
value={tab}
onValueChange={setTab}
className="w-full mx-auto my-8 text-center"
>
<TabsList>
<TabsTrigger value="all">All Matches</TabsTrigger>
<TabsTrigger value="coming">Coming Matches</TabsTrigger>
</TabsList>
<TabsContent value="all">
<AllMatches />
</TabsContent>
<TabsContent value="coming">
<MatchDay
id="coming"
title="Coming Matches"
matches={comingMatches}
/>
</TabsContent>
</Tabs> */}
</main>
{isVisible && (
<aside className="right-8 bottom-16 fixed">
<Button
variant="outline"
size="icon"
onClick={() => {
window.scrollTo({
top: 0,
behavior: "smooth",
});
}}
>
<FaChevronUp />
</Button>
</aside>
)}
{wallet.publicKey && userAccount === null && !isScoreLoading && (
<aside className="hidden sm:block right-4 pt-8 fixed">
<Button onClick={() => registerUser()}>Mint BONK</Button>
</aside>
)}
</div>
);
}
const AllMatches = () => {
return (
<>
<MatchDay
id="matchday1"
title="Matchday 1"
matches={matchesFirstMatchDay}
/>
<MatchDay
id="matchday2"
title="Matchday 2"
matches={matchesSecondMatchday}
/>
<MatchDay
id="matchday3"
title="Matchday 3"
matches={matchesThirdMatchday}
/>
<MatchDay id="round16" title="Round of 16" matches={matchesRound16} />
<MatchDay
id="quarter"
title="Quarter Finals"
matches={matchesQuarterFinals}
/>
<MatchDay id="semi" title="Semi Finals" matches={matchesSemiFinals} />
<MatchDay id="final" title="Final" matches={matchesFinal} />
</>
);
};
================================================
FILE: app/providers.tsx
================================================
"use client";
import { ThemeProvider } from "@/components/ui/theme-provider";
import { ContextProvider } from "@/contexts/ContextProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { useState } from "react";
export default function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
})
);
return (
<ContextProvider>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</ThemeProvider>
</ContextProvider>
);
}
================================================
FILE: components/TableRow.tsx
================================================
import { cn } from "@/lib/utils";
import Image from "next/image";
import React from "react";
interface TableRowProps {
number: number;
name: string;
points: string;
isGold?: boolean;
highlight: boolean;
}
const TableRow: React.FC<TableRowProps> = ({
number,
name,
points,
isGold,
highlight,
}) => {
const hoverClasses =
highlight && number !== 1
? "sm:scale-110 shadow-[0px_5px_15px_8px_#e4e7fb] !bg-yellow-300 "
: "";
return (
<tr
className={cn(
"bg-white odd:bg-gray-100 transition-all duration-200 ease-in-out rounded-md",
hoverClasses
)}
>
<td className="h-[5rem] font-rubik text-[2.2rem] font-bold text-left">
{number}
</td>
<td className="h-[5rem] font-rubik text-[1.2rem] text-left">{name}</td>
<td className="h-[5rem] font-rubik text-[1.3rem] font-bold flex justify-end items-center">
{points}
{isGold && (
<Image
width={50}
height={50}
className="h-[3rem] ml-[1.5rem] hidden sm:block"
src="https://github.com/malunaridev/Challenges-iCodeThis/blob/master/4-leaderboard/assets/gold-medal.png?raw=true"
alt="gold medal"
/>
)}
</td>
</tr>
);
};
export default TableRow;
================================================
FILE: components/connect-wallet-button.tsx
================================================
"use client";
import { Button } from "./ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "./ui/dropdown-menu";
import {
useAnchorWallet,
useConnection,
useWallet,
} from "@solana/wallet-adapter-react";
import { useWalletModal } from "@solana/wallet-adapter-react-ui";
import { RiArrowDropDownLine } from "react-icons/ri";
import { TbCopy } from "react-icons/tb";
import { toast } from "./ui/use-toast";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "./ui/tooltip";
import useAnchorProgram from "@/hooks/useAnchorProgram";
import { Skeleton } from "./ui/skeleton";
import { useUserAccount } from "@/hooks/queries/useUserAccount";
import { useUserSolBalance } from "@/hooks/queries/useUserSolBalance";
import { FaWallet } from "react-icons/fa";
import { useUserBonkBalance } from "@/hooks/queries/useUserBonkBalance";
import { useRegisterUser } from "@/hooks/mutations/useRegisterUser";
import { useAllUserAccounts } from "@/hooks/queries/useAllUserAccounts";
import { useAirdropSol } from "@/hooks/mutations/useAirdropSol";
const ConnectWalletButton = () => {
const wallet = useWallet();
const anchorWallet = useAnchorWallet();
const { disconnect, connected, publicKey } = useWallet();
const program = useAnchorProgram();
const { connection } = useConnection();
const { setVisible } = useWalletModal();
const { data: userAccount, isLoading: isAccountLoading } = useUserAccount(
program,
connection,
publicKey
);
const { data: allScores, isLoading: isScoresLoading } =
useAllUserAccounts(program);
const rank = allScores?.findIndex(
(element) =>
element.account.userAddress.toBase58() === wallet.publicKey?.toBase58()
);
const { data: solBalance, isLoading: isSolBalanceLoading } =
useUserSolBalance(connection, wallet?.publicKey ?? null);
const { data: bonkBalance, isLoading: isBonkBalanceLoading } =
useUserBonkBalance(program, connection, wallet?.publicKey ?? null);
const { mutate: registerUser, isPending: isMintingBonkPending } =
useRegisterUser(program, connection, wallet);
const { mutate: airdropSol, isPending: isAirdropPending } = useAirdropSol(
connection,
wallet
);
if (!connected) {
return (
<Button
onClick={() => {
setVisible(true);
}}
>
<div className="flex gap-2 items-center">
<FaWallet />
<div className="hidden sm:block">Connect wallet</div>
</div>
</Button>
);
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button>
<div className="flex gap-2 items-center">
<FaWallet className="sm:hidden" />
<div className="hidden sm:block">Connected</div>
</div>
<RiArrowDropDownLine className="text-xl ml-2" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="end" forceMount>
<DropdownMenuItem className="font-normal">
{anchorWallet && (
<TooltipProvider delayDuration={200}>
<Tooltip>
<TooltipTrigger>
<div
className="flex"
onClick={() => {
navigator.clipboard.writeText(
anchorWallet.publicKey?.toBase58() ?? ""
);
toast({ variant: "default", title: "Copied!" });
}}
>
<div>
{anchorWallet?.publicKey?.toBase58().slice(0, 4) +
"..." +
anchorWallet?.publicKey?.toBase58().slice(-4)}
</div>
<TbCopy className="ml-2" />
</div>
</TooltipTrigger>
<TooltipContent side="left">
<div>{wallet?.publicKey?.toBase58()}</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<div className="border rounded-md py-2 px-1 my-2">
<div className="flex flex-col mx-2 gap-2 text-sm">
<div className="flex items-center justify-between">
<div>Score:</div>
{!isAccountLoading ? (
userAccount ? (
<div>{userAccount.score.toFixed(2)}</div>
) : (
<div>100.0</div>
)
) : (
<Skeleton className="w-6 h-4 rounded-md" />
)}
</div>
<div className="flex items-center justify-between">
<div>Rank:</div>
{!isScoresLoading ? (
rank !== undefined ? (
<div>{rank + 1}</div>
) : (
<div>-</div>
)
) : (
<Skeleton className="w-6 h-4 rounded-md" />
)}
</div>
<div className="flex items-center justify-between">
<div>Bonk Balance:</div>
{userAccount === null && !isAccountLoading ? (
<Button
size={"xs"}
disabled={isMintingBonkPending}
onClick={() => registerUser()}
className="flex text-center justify-center text-xs"
>
Mint
</Button>
) : !isBonkBalanceLoading ? (
<div>{bonkBalance?.toFixed(2)}</div>
) : (
<Skeleton className="w-6 h-4 rounded-md" />
)}
</div>
<div className="flex items-center justify-between">
<div>Sol Balance:</div>
{solBalance !== undefined ? (
solBalance > 0.01 ? (
<div>{solBalance?.toFixed(2)}</div>
) : (
<Button
size={"xs"}
disabled={isAirdropPending}
onClick={() => airdropSol()}
className="flex text-center justify-center text-xs"
>
Mint Sol
</Button>
)
) : (
<Skeleton className="w-6 h-4 rounded-md" />
)}
</div>
</div>
</div>
</DropdownMenuGroup>
{wallet && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={disconnect}
className="hover:cursor-pointer"
>
Disconnect
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
);
};
export default ConnectWalletButton;
================================================
FILE: components/dark-mode-toggle.tsx
================================================
"use client";
import * as React from "react";
import { useTheme } from "next-themes";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { RxMoon, RxSun } from "react-icons/rx";
export function DarkModeToggle() {
const { setTheme, theme } = useTheme();
console.log("Theme", theme);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<RxSun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<RxMoon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
================================================
FILE: components/footer.tsx
================================================
import Link from "next/link";
import { FaTelegramPlane } from "react-icons/fa";
import { FaDiscord, FaXTwitter } from "react-icons/fa6";
export const Footer = () => {
return (
<footer className="bg-gray-800 text-white py-2 bottom-0 w-full">
<div className="flex justify-center space-x-8">
<Link
target="_blank"
rel="noopener noreferrer"
href={"https://discord.gg/HNxstUVC"}
>
<FaDiscord size={40} />
</Link>
<Link
target="_blank"
rel="noopener noreferrer"
href={"https://twitter.com/ProofOfEstimate"}
>
<FaXTwitter size={40} />
</Link>
<Link
target="_blank"
rel="noopener noreferrer"
href={"https://t.me/+19Jfbq7Pl1phNWIy"}
>
<FaTelegramPlane size={40} />
</Link>
</div>
<div className="text-center text-sm">
© {new Date().getFullYear()} Poe. All rights reserved.
</div>
</footer>
);
};
================================================
FILE: components/market-stats.tsx
================================================
"use client";
import useAnchorProgram from "@/hooks/useAnchorProgram";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
import { Skeleton } from "./ui/skeleton";
import { usePollById } from "@/hooks/queries/usePollById";
import { Separator } from "./ui/separator";
const MarketStats = ({
matchId,
profitScore,
reputationScore,
}: {
matchId: number;
profitScore: string;
reputationScore: string;
}) => {
const program = useAnchorProgram();
const { data: poll, isLoading: isLoadingPoll } = usePollById(
program,
matchId,
true
);
return (
<Card className="h-fit">
<CardHeader>
<CardTitle className="text-lg">Additional Data 📊</CardTitle>
</CardHeader>
<CardContent>
{poll ? (
<div className="">
{poll.numForecasters.toString()} participant
{poll.numForecasters.toNumber() !== 1 ? "s" : ""}
</div>
) : (
<Skeleton className="w-8 h-5 rounded-md" />
)}
<Separator className="my-2" />
<div className="flex gap-4">
<p>Your profit:</p>
<p>{profitScore}</p>
</div>
<div className="flex gap-4">
<p>Your score:</p>
<p>{reputationScore}</p>
</div>
</CardContent>
</Card>
);
};
export default MarketStats;
================================================
FILE: components/match-card.tsx
================================================
"use client";
import { Match } from "@/lib/dummyData";
import { Button } from "./ui/button";
import { Slider } from "./ui/slider";
import Image from "next/image";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "./ui/card";
import { Separator } from "./ui/separator";
import Link from "next/link";
import { RiArrowRightDoubleLine } from "react-icons/ri";
import useAnchorProgram from "@/hooks/useAnchorProgram";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { useUserEstimateByPoll } from "@/hooks/queries/useUserEstimateByPoll";
import { useMakeEstimate } from "@/hooks/mutations/useMakeEstimate";
import { useUserScore } from "@/hooks/queries/useUserScore";
import { useUpdateEstimate } from "@/hooks/mutations/useUpdateEstimate";
import { useCollectPoints } from "@/hooks/mutations/useCollectPoints";
import { useEffect, useRef, useState } from "react";
import { usePollById } from "@/hooks/queries/usePollById";
import { Skeleton } from "./ui/skeleton";
import { TbLoader2 } from "react-icons/tb";
import useIntersectionObserver from "@/hooks/useIntersectionObserver";
import clsx from "clsx";
import { Badge } from "./ui/badge";
export const MatchCard = ({ match }: { match: Match }) => {
const program = useAnchorProgram();
const { connection } = useConnection();
const wallet = useWallet();
const ref = useRef<HTMLDivElement | null>(null);
const isVisible =
useIntersectionObserver(ref, { threshold: 0.1 }) &&
match.teamA !== "tbd" &&
match.teamB !== "tbd";
const matchId = Number.parseInt(match.id) - 1;
const {
data: userEstimate,
isError: isErrorEstimate,
error: errorEstimate,
isLoading: isLoadingEstimate,
} = useUserEstimateByPoll(
program,
connection,
wallet.publicKey,
matchId,
isVisible
);
const {
data: poll,
isLoading: isLoadingPoll,
isError: isErrorPoll,
error: errorPoll,
} = usePollById(program, matchId, isVisible);
const { data: userScore, isLoading: isLoadingScore } = useUserScore(
program,
connection,
wallet.publicKey,
matchId,
isVisible
);
const { mutate: submitEstimate, isPending: isSubmitting } = useMakeEstimate(
program,
connection,
wallet
);
const { mutate: updateEstimate, isPending: isUpdating } = useUpdateEstimate(
program,
connection,
wallet
);
const { mutate: collectPoints, isPending: isCollecting } = useCollectPoints(
program,
connection,
wallet
);
const [estimate, setEstimate] = useState(
userEstimate !== null && userEstimate !== undefined
? (userEstimate.lowerEstimate + userEstimate.upperEstimate) / 2
: undefined
);
useEffect(() => {
if (userEstimate !== null && userEstimate !== undefined) {
setEstimate(
(userEstimate.lowerEstimate + userEstimate.upperEstimate) / 2
);
}
}, [userEstimate]);
const handleChange = (estimate: [number]) => {
setEstimate(estimate[0]);
};
const isLive = poll?.hasStarted && poll?.result == null;
return (
<Card ref={ref} className="w-full mx-4 sm:mx-0 sm:w-[25rem]">
<CardHeader>
<CardDescription>
<div className="flex justify-between">
{match.date}
{isLive && (
<Badge>
<span className="block w-[6px] h-[6px] bg-red-500 rounded-full drop-shadow-[0_0px_8px_#F7931A10)] animate-ping mr-2"></span>
Live
</Badge>
)}
</div>
</CardDescription>
<CardTitle className="flex w-1/2 gap-4 items-center">
<Image
width={36}
height={27}
alt="Flag of team A"
src={match.logoA ? match.logoA : "https://via.placeholder.com/50"}
/>
<span className="text-lg font-bold">{match.teamA}</span>
<span className="ml-auto">{match.resultA}</span>
</CardTitle>
<CardTitle className="flex w-1/2 gap-4 pt-4 items-center">
<Image
width={36}
height={27}
alt="Flag of team B"
src={match.logoB ? match.logoB : "https://via.placeholder.com/50"}
/>
<span className="text-lg font-bold">{match.teamB}</span>
<span className="ml-auto">{match.resultB}</span>
</CardTitle>
</CardHeader>
<CardContent>
<Separator className="mb-4" />
<p className="block text-md font-semibold mb-2">
Prob. that {match.teamA} wins
</p>
<div className="flex w-3/5 sm:w-1/2 justify-between">
<p className="block text-sm">Market Prediction:</p>
<p className="text-sm font-bold">
{poll && poll.collectiveEstimate !== null
? (poll.collectiveEstimate / 10000).toFixed(0) + "%"
: "-"}
</p>
</div>
<div className="flex w-3/5 sm:w-1/2 justify-between">
<p className="block text-sm">Your Prediction:</p>
<p
className={clsx(
"text-sm font-bold",
estimate !== userEstimate?.lowerEstimate
? "dark:text-yellow-300"
: ""
)}
>
{estimate !== undefined ? estimate + "%" : "-"}
</p>
</div>
<div className="flex gap-4 items-center">
<Slider
onValueChange={handleChange}
value={[estimate !== undefined ? estimate : 50]}
max={100}
step={1}
className="my-4"
disabled={poll?.result !== null}
/>
<Button
variant={"secondary"}
disabled={poll?.result !== null}
size={"sm"}
onClick={() => {
if (userEstimate !== null && userEstimate !== undefined) {
setEstimate(
(userEstimate.lowerEstimate + userEstimate.upperEstimate) / 2
);
} else {
setEstimate(undefined);
}
}}
>
Reset
</Button>
</div>
</CardContent>
<CardFooter>
<div className="w-full flex gap-4 items-center justify-between">
{isLoadingPoll ? (
<Skeleton className="w-3/5 h-9 rounded-md" />
) : poll?.result !== null ? (
userScore === null || userScore === undefined || isLoadingScore ? (
<div>
{poll !== undefined ? (
poll.result ? (
<div className="text-primary font-bold">
{match.teamA} won!
</div>
) : (
<div className="text-primary font-bold">
{match.teamA} did not win!
</div>
)
) : (
""
)}
</div>
) : (
<Button
disabled={isCollecting}
className="w-3/5"
onClick={() => collectPoints({ pollId: matchId })}
>
{isCollecting && (
<TbLoader2 className="mr-2 h-4 w-4 animate-spin" />
)}
Withdraw your funds
</Button>
)
) : userEstimate !== undefined && userEstimate !== null ? (
<Button
disabled={
isUpdating ||
(estimate === userEstimate.lowerEstimate &&
estimate === userEstimate.upperEstimate)
}
className="w-3/5"
onClick={() =>
updateEstimate({
pollId: matchId,
lowerEstimate: estimate,
upperEstimate: estimate,
})
}
>
{isUpdating && (
<TbLoader2 className="mr-2 h-4 w-4 animate-spin" />
)}
Update Estimate
</Button>
) : (
<Button
disabled={isSubmitting || estimate === undefined}
className="font-bold rounded w-full "
onClick={() =>
submitEstimate({
pollId: matchId,
lowerEstimate: estimate,
upperEstimate: estimate,
})
}
>
{isSubmitting && (
<TbLoader2 className="mr-2 h-4 w-4 animate-spin" />
)}
Bet 100 BONK
</Button>
)}
<Button size={"sm"} variant={"ghost"} asChild>
<Link className="text-xs" href={"/match/" + match.id}>
<RiArrowRightDoubleLine className="mr-2" /> Details
</Link>
</Button>
</div>
</CardFooter>
</Card>
);
};
================================================
FILE: components/match-day.tsx
================================================
import { Match } from "@/lib/dummyData";
import { MatchCard } from "./match-card";
export const MatchDay = ({
id,
title,
matches,
}: {
id: string;
title: string;
matches: Match[];
}) => {
return (
<div className="mb-8">
<div className="flex gap-4 items-center mb-8 justify-center">
<h2 id={id} className="text-4xl font-bold text-center">
{title}
</h2>
</div>
<div className={`flex flex-wrap justify-center gap-5 md:gap-x-10`}>
{matches.map((match: Match) => (
<MatchCard key={match.id} match={match} />
))}
</div>
</div>
);
};
================================================
FILE: components/nav-bar.tsx
================================================
import Link from "next/link";
import ConnectWalletButton from "./connect-wallet-button";
import Image from "next/image";
import QuickTourDialog from "./quick-tour-dialog";
import { DarkModeToggle } from "./dark-mode-toggle";
require("@solana/wallet-adapter-react-ui/styles.css");
export const NavBar = () => {
return (
<header>
<div className="border-b px-4 py-4">
<div className="flex h-16 items-center gap-1 sm:gap-8">
{/* Keep it as comment because I think it will be used soon */}
{/* <MainNav /> */}
{/* <MobileNav /> */}
<Link
href="/"
className="text-sm font-medium hover:cursor-pointer"
prefetch={false}
>
<p className="text-lg">UEFA 2024</p>
<span className="sm:flex hidden items-center gap-1 text-xs">
powered by
<Image width={20} height={20} alt="Logo" src={"/Poe.png"} />
</span>
</Link>
<Link
href="/leaderboard"
className="ml-auto text-sm font-medium hover:bg-yellow-500 p-2 hover:bg-opacity-20 rounded-md"
prefetch={false}
>
Leaderboard
</Link>
<div className="flex items-center md:space-x-4">
<ConnectWalletButton />
</div>
<DarkModeToggle />
</div>
</div>
</header>
);
};
================================================
FILE: components/quick-tour-dialog.tsx
================================================
"use client";
import useAnchorProgram from "@/hooks/useAnchorProgram";
import { Button } from "./ui/button";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "./ui/carousel";
import { Dialog, DialogContent, DialogTrigger } from "./ui/dialog";
import { useConnection, useWallet } from "@solana/wallet-adapter-react";
import { useUserAccount } from "@/hooks/queries/useUserAccount";
import { useRegisterUser } from "@/hooks/mutations/useRegisterUser";
const QuickTourDialog = () => {
const program = useAnchorProgram();
const { connection } = useConnection();
const wallet = useWallet();
const { data: userAccount, isLoading: isScoreLoading } = useUserAccount(
program,
connection,
wallet.publicKey
);
const { mutate: registerUser } = useRegisterUser(program, connection, wallet);
return (
<Dialog>
<DialogTrigger>
<Button asChild>
<div>Quick Tour</div>
</Button>
</DialogTrigger>
<DialogContent>
<Carousel>
<CarouselContent>
<CarouselItem>
<div className="flex flex-col gap-4">
<div className="text-lg md:text-xl font-bold">
Predict Probabilities, not just outcomes!
</div>
<div className="w-5/6 sm:w-full">
With Poe, you predict how likely something is to happen. Your
betting stake goes into a pool. Poe uses a special system to
score your forecast and determine your payout.
</div>
</div>
</CarouselItem>
<CarouselItem>
<div className="flex flex-col gap-4">
<div className="text-lg md:text-xl font-bold">
Always in the Game!
</div>
<div className="w-5/6 sm:w-full">
Update your beliefs as you learn more and the match
progresses. Poe will calculate a time-averaged score after the
end of the match which determines your payout.
</div>
</div>
</CarouselItem>
<CarouselItem>
<div className="flex flex-col gap-4">
<div className="text-lg md:text-xl font-bold">
Get rewarded for your insights!
</div>
<div className="w-5/6 sm:w-full">
Just submit your beliefs. If you are right, you make a profit
in expectation. Mint your 4000 BONK token to get started!
</div>
{userAccount === null && !isScoreLoading ? (
<Button className="w-1/2" onClick={() => registerUser()}>
Mint BONK
</Button>
) : (
<Button className="w-1/2" disabled>
Bonk already minted!
</Button>
)}
</div>
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</DialogContent>
</Dialog>
);
};
export default QuickTourDialog;
================================================
FILE: components/sidenav.tsx
================================================
"use client";
import QuickTourDialog from "./quick-tour-dialog";
export default function SideNav() {
return (
<div className="flex flex-col items-start py-4 px-10 gap-8 mt-4 pb-40 fixed h-full overflow-scroll">
<QuickTourDialog />
<SideNavItem id="matchday1" title="Matchday 1" />
<SideNavItem id="matchday2" title="Matchday 2" />
<SideNavItem id="matchday3" title="Matchday 3" />
<SideNavItem id="round16" title="Round of 16" />
<SideNavItem id="quarter" title="Quarter Finals" />
<SideNavItem id="semi" title="Semi Finals" />
<SideNavItem id="final" title="Finals" />
</div>
);
}
function scrollTo(id: string) {
const element = document.getElementById(id);
element?.scrollIntoView({ behavior: "smooth" });
}
const SideNavItem = ({ id, title }: { id: string; title: string }) => {
return (
<div
onClick={() => scrollTo(id)}
className="text-lg p-2 rounded-md font-semibold hover:cursor-pointer hover:bg-slate-300"
>
{title}
</div>
);
};
================================================
FILE: components/ui/avatar.tsx
================================================
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }
================================================
FILE: components/ui/badge.tsx
================================================
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }
================================================
FILE: components/ui/button.tsx
================================================
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
xs: "h-5 rounded-md px-2 text-xs",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = "Button";
export { Button, buttonVariants };
================================================
FILE: components/ui/card.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
================================================
FILE: components/ui/carousel.tsx
================================================
"use client";
import * as React from "react";
import { ArrowLeftIcon, ArrowRightIcon } from "@radix-ui/react-icons";
import useEmblaCarousel, {
type UseEmblaCarouselType,
} from "embla-carousel-react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps = {
opts?: CarouselOptions;
plugins?: CarouselPlugin;
orientation?: "horizontal" | "vertical";
setApi?: (api: CarouselApi) => void;
};
type CarouselContextProps = {
carouselRef: ReturnType<typeof useEmblaCarousel>[0];
api: ReturnType<typeof useEmblaCarousel>[1];
scrollPrev: () => void;
scrollNext: () => void;
canScrollPrev: boolean;
canScrollNext: boolean;
} & CarouselProps;
const CarouselContext = React.createContext<CarouselContextProps | null>(null);
function useCarousel() {
const context = React.useContext(CarouselContext);
if (!context) {
throw new Error("useCarousel must be used within a <Carousel />");
}
return context;
}
const Carousel = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & CarouselProps
>(
(
{
orientation = "horizontal",
opts,
setApi,
plugins,
className,
children,
...props
},
ref
) => {
const [carouselRef, api] = useEmblaCarousel(
{
...opts,
axis: orientation === "horizontal" ? "x" : "y",
},
plugins
);
const [canScrollPrev, setCanScrollPrev] = React.useState(false);
const [canScrollNext, setCanScrollNext] = React.useState(false);
const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) {
return;
}
setCanScrollPrev(api.canScrollPrev());
setCanScrollNext(api.canScrollNext());
}, []);
const scrollPrev = React.useCallback(() => {
api?.scrollPrev();
}, [api]);
const scrollNext = React.useCallback(() => {
api?.scrollNext();
}, [api]);
const handleKeyDown = React.useCallback(
(event: React.KeyboardEvent<HTMLDivElement>) => {
if (event.key === "ArrowLeft") {
event.preventDefault();
scrollPrev();
} else if (event.key === "ArrowRight") {
event.preventDefault();
scrollNext();
}
},
[scrollPrev, scrollNext]
);
React.useEffect(() => {
if (!api || !setApi) {
return;
}
setApi(api);
}, [api, setApi]);
React.useEffect(() => {
if (!api) {
return;
}
onSelect(api);
api.on("reInit", onSelect);
api.on("select", onSelect);
return () => {
api?.off("select", onSelect);
};
}, [api, onSelect]);
return (
<CarouselContext.Provider
value={{
carouselRef,
api: api,
opts,
orientation:
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
scrollPrev,
scrollNext,
canScrollPrev,
canScrollNext,
}}
>
<div
ref={ref}
onKeyDownCapture={handleKeyDown}
className={cn("relative", className)}
role="region"
aria-roledescription="carousel"
{...props}
>
{children}
</div>
</CarouselContext.Provider>
);
}
);
Carousel.displayName = "Carousel";
const CarouselContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { carouselRef, orientation } = useCarousel();
return (
<div ref={carouselRef} className="overflow-hidden">
<div
ref={ref}
className={cn(
"flex",
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
className
)}
{...props}
/>
</div>
);
});
CarouselContent.displayName = "CarouselContent";
const CarouselItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const { orientation } = useCarousel();
return (
<div
ref={ref}
role="group"
aria-roledescription="slide"
className={cn(
"min-w-0 shrink-0 grow-0 basis-full",
orientation === "horizontal" ? "pl-4" : "pt-4",
className
)}
{...props}
/>
);
});
CarouselItem.displayName = "CarouselItem";
const CarouselPrevious = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollPrev, canScrollPrev } = useCarousel();
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-left-12 top-1/2 -translate-y-1/2"
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollPrev}
onClick={scrollPrev}
{...props}
>
<ArrowLeftIcon className="h-4 w-4" />
<span className="sr-only">Previous slide</span>
</Button>
);
});
CarouselPrevious.displayName = "CarouselPrevious";
const CarouselNext = React.forwardRef<
HTMLButtonElement,
React.ComponentProps<typeof Button>
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
const { orientation, scrollNext, canScrollNext } = useCarousel();
return (
<Button
ref={ref}
variant={variant}
size={size}
className={cn(
"absolute h-8 w-8 rounded-full",
orientation === "horizontal"
? "-right-12 top-1/2 -translate-y-1/2"
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
className
)}
disabled={!canScrollNext}
onClick={scrollNext}
{...props}
>
<ArrowRightIcon className="h-4 w-4" />
<span className="sr-only">Next slide</span>
</Button>
);
});
CarouselNext.displayName = "CarouselNext";
export {
type CarouselApi,
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
};
================================================
FILE: components/ui/dialog.tsx
================================================
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
================================================
FILE: components/ui/dropdown-menu.tsx
================================================
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<DotFilledIcon className="h-4 w-4 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
================================================
FILE: components/ui/input.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }
================================================
FILE: components/ui/separator.tsx
================================================
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }
================================================
FILE: components/ui/skeleton.tsx
================================================
import { cn } from "@/lib/utils"
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-primary/10", className)}
{...props}
/>
)
}
export { Skeleton }
================================================
FILE: components/ui/slider.tsx
================================================
"use client";
import * as React from "react";
import * as SliderPrimitive from "@radix-ui/react-slider";
import { cn } from "@/lib/utils";
const Slider = React.forwardRef<
React.ElementRef<typeof SliderPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
>(({ className, ...props }, ref) => (
<SliderPrimitive.Root
ref={ref}
className={cn(
"relative flex w-full touch-none select-none items-center",
className
)}
{...props}
>
<SliderPrimitive.Track className="relative h-8 w-full grow overflow-hidden rounded-lg bg-primary/20">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb className="block h-8 w-1 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" />
</SliderPrimitive.Root>
));
Slider.displayName = SliderPrimitive.Root.displayName;
export { Slider };
================================================
FILE: components/ui/table.tsx
================================================
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}
================================================
FILE: components/ui/tabs.tsx
================================================
"use client"
import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"
import { cn } from "@/lib/utils"
const Tabs = TabsPrimitive.Root
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className
)}
{...props}
/>
))
TabsList.displayName = TabsPrimitive.List.displayName
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
className
)}
{...props}
/>
))
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
))
TabsContent.displayName = TabsPrimitive.Content.displayName
export { Tabs, TabsList, TabsTrigger, TabsContent }
================================================
FILE: components/ui/theme-provider.tsx
================================================
"use client";
import * as React from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { type ThemeProviderProps } from "next-themes/dist/types";
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
}
================================================
FILE: components/ui/toast.tsx
================================================
"use client"
import * as React from "react"
import { Cross2Icon } from "@radix-ui/react-icons"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
)}
toast-close=""
{...props}
>
<Cross2Icon className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}
================================================
FILE: components/ui/toaster.tsx
================================================
"use client"
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
import { useToast } from "@/components/ui/use-toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}
================================================
FILE: components/ui/tooltip.tsx
================================================
"use client";
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
================================================
FILE: components/ui/use-toast.ts
================================================
"use client"
// Inspired by react-hot-toast library
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }
================================================
FILE: components.json
================================================
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
================================================
FILE: contexts/AutoConnectProvider.tsx
================================================
"use client";
import { useLocalStorage } from "@solana/wallet-adapter-react";
import { createContext, FC, ReactNode, useContext } from "react";
export interface AutoConnectContextState {
autoConnect: boolean;
setAutoConnect(autoConnect: boolean): void;
}
export const AutoConnectContext = createContext<AutoConnectContextState>(
{} as AutoConnectContextState
);
export function useAutoConnect(): AutoConnectContextState {
return useContext(AutoConnectContext);
}
export const AutoConnectProvider: FC<{ children: ReactNode }> = ({
children,
}) => {
// TODO: fix auto connect to actual reconnect on refresh/other.
// TODO: make switch/slider settings
// const [autoConnect, setAutoConnect] = useLocalStorage('autoConnect', false);
const [autoConnect, setAutoConnect] = useLocalStorage("autoConnect", true);
return (
<AutoConnectContext.Provider value={{ autoConnect, setAutoConnect }}>
{children}
</AutoConnectContext.Provider>
);
};
================================================
FILE: contexts/ContextProvider.tsx
================================================
"use client";
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
import {
ConnectionProvider,
WalletProvider,
} from "@solana/wallet-adapter-react";
import {
PhantomWalletAdapter,
SolflareWalletAdapter,
} from "@solana/wallet-adapter-wallets";
import { clusterApiUrl } from "@solana/web3.js";
import { ComponentType, FC, ReactNode, useMemo } from "react";
import { AutoConnectProvider, useAutoConnect } from "./AutoConnectProvider";
import {
NetworkConfigurationProvider,
useNetworkConfiguration,
} from "./NetworkConfigurationProvider";
import dynamic from "next/dynamic";
import { Toaster } from "@/components/ui/toaster";
const ReactUIWalletModalProviderDynamic: ComponentType<{
children: ReactNode;
}> = dynamic(
async () =>
(await import("@solana/wallet-adapter-react-ui")).WalletModalProvider,
{ ssr: false }
);
const WalletContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
const { autoConnect } = useAutoConnect();
const { networkConfiguration } = useNetworkConfiguration();
const network = networkConfiguration as WalletAdapterNetwork;
const endpoint = useMemo(() => clusterApiUrl(network), [network]);
const wallets = useMemo(
() => [new SolflareWalletAdapter(), new PhantomWalletAdapter()],
// eslint-disable-next-line react-hooks/exhaustive-deps
[network]
);
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect={autoConnect}>
<ReactUIWalletModalProviderDynamic>
{children}
</ReactUIWalletModalProviderDynamic>
</WalletProvider>
<Toaster />
</ConnectionProvider>
);
};
export const ContextProvider: FC<{ children: ReactNode }> = ({ children }) => {
return (
<>
<NetworkConfigurationProvider>
<AutoConnectProvider>
<WalletContextProvider>{children}</WalletContextProvider>
</AutoConnectProvider>
</NetworkConfigurationProvider>
</>
);
};
================================================
FILE: contexts/NetworkConfigurationProvider.tsx
================================================
"use client";
import { useLocalStorage } from "@solana/wallet-adapter-react";
import { createContext, FC, ReactNode, useContext } from "react";
export interface NetworkConfigurationState {
networkConfiguration: string;
setNetworkConfiguration(networkConfiguration: string): void;
}
export const NetworkConfigurationContext =
createContext<NetworkConfigurationState>({} as NetworkConfigurationState);
export function useNetworkConfiguration(): NetworkConfigurationState {
return useContext(NetworkConfigurationContext);
}
export const NetworkConfigurationProvider: FC<{ children: ReactNode }> = ({
children,
}) => {
const [networkConfiguration, setNetworkConfiguration] = useLocalStorage(
"network",
"devnet"
);
return (
<NetworkConfigurationContext.Provider
value={{ networkConfiguration, setNetworkConfiguration }}
>
{children}
</NetworkConfigurationContext.Provider>
);
};
================================================
FILE: errors/NoUserAccountError.ts
================================================
export class NoUserAccountError extends Error {
constructor(message: string) {
super(message);
this.name = "No User Account!";
}
}
================================================
FILE: errors/WalletNotConnectedError.ts
================================================
export class WalletNotConnectedError extends Error {
constructor(message: string) {
super(message);
this.name = "Wallet not connected!";
}
}
================================================
FILE: hooks/mutations/useAirdropSol.tsx
================================================
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, LAMPORTS_PER_SOL } from "@solana/web3.js";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { useToast } from "@/components/ui/use-toast";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
const airdropSol = async (
connection: Connection,
wallet: WalletContextState
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
const [latestBlockhash, signature] = await Promise.all([
connection.getLatestBlockhash(),
connection.requestAirdrop(wallet.publicKey, 1 * LAMPORTS_PER_SOL),
]);
await connection.confirmTransaction(
{ signature, ...latestBlockhash },
"confirmed"
);
};
const useAirdropSol = (connection: Connection, wallet: WalletContextState) => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: () => airdropSol(connection, wallet),
onSuccess: () => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "Airdrop was confirmed",
});
queryClient.invalidateQueries({
queryKey: [userSolBalanceKey],
});
},
onError: (e) => {
toast({
variant: "destructive",
title: e.name,
description: e.message,
});
},
});
};
export { useAirdropSol };
================================================
FILE: hooks/mutations/useCollectPoints.ts
================================================
import { Poe } from "@/idl/poe";
import { BN, Program } from "@coral-xyz/anchor";
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, PublicKey } from "@solana/web3.js";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { userAccountKey } from "../queries/useUserAccount";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { pollByIdKey } from "../queries/usePollById";
import { userScoreKey } from "../queries/useUserScore";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
import { useToast } from "@/components/ui/use-toast";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { sendVersionedTransaction } from "../../utils/sendVersionedTransaction";
import { getAssociatedTokenAddress } from "@solana/spl-token";
import { allPollsByUserKey } from "../queries/useAllPollsByUser";
import { allUserAccounts } from "../queries/useAllUserAccounts";
import { userBonkBalanceKey } from "../queries/useUserBonkBalance";
const collectPoints = async (
program: Program<Poe>,
connection: Connection,
wallet: WalletContextState,
pollId: number
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
let [userPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), wallet.publicKey.toBuffer()],
program.programId
);
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let [userPredictionPda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let [scoreListPda] = PublicKey.findProgramAddressSync(
[Buffer.from("scoring_list"), pollPda.toBuffer()],
program.programId
);
let [userScorePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_score"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let [mintPda, mintBump] = PublicKey.findProgramAddressSync(
[Buffer.from("poeken_mint")],
program.programId
);
let [escrowPda, _escrowBump] = PublicKey.findProgramAddressSync(
[Buffer.from("escrow")],
program.programId
);
const tokenAccountAddress = await getAssociatedTokenAddress(
mintPda,
wallet.publicKey
);
const registerUserInstruction = await program.methods
.collectPoints()
.accountsPartial({
user: userPda,
forecaster: wallet.publicKey,
poll: pollPda,
userEstimate: userPredictionPda,
scoringList: scoreListPda,
userScore: userScorePda,
mint: mintPda,
escrowAccount: escrowPda,
forecasterTokenAccount: tokenAccountAddress,
})
.instruction();
await sendVersionedTransaction([registerUserInstruction], wallet, connection);
};
const useCollectPoints = (
program: Program<Poe>,
connection: Connection,
wallet: WalletContextState
) => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: ({ pollId }: { pollId: number }) =>
collectPoints(program, connection, wallet, pollId),
onSuccess: (_, variables) => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "Points collected.",
});
queryClient.invalidateQueries({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [allPollsByUserKey, wallet.publicKey?.toBase58()],
});
queryClient.invalidateQueries({
queryKey: [pollByIdKey, variables.pollId],
});
queryClient.invalidateQueries({
queryKey: [
userScoreKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [allUserAccounts],
});
queryClient.invalidateQueries({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userBonkBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
},
onError: (e) => {
toast({ variant: "destructive", title: e.name, description: e.message });
},
});
};
export { useCollectPoints };
================================================
FILE: hooks/mutations/useMakeEstimate.tsx
================================================
import React from "react";
import { Poe } from "@/idl/poe";
import { BN, Program } from "@coral-xyz/anchor";
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, PublicKey, TransactionInstruction } from "@solana/web3.js";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { pollByIdKey } from "../queries/usePollById";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { useToast } from "@/components/ui/use-toast";
import { NoUserAccountError } from "@/errors/NoUserAccountError";
import { ToastAction } from "@/components/ui/toast";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { useWalletModal } from "@solana/wallet-adapter-react-ui";
import { userAccountKey } from "../queries/useUserAccount";
import { sendVersionedTransaction } from "../../utils/sendVersionedTransaction";
import { getAssociatedTokenAddress } from "@solana/spl-token";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { userScoreKey } from "../queries/useUserScore";
import { userEstimateKey } from "../queries/useUserEstimateByPoll";
import { useRegisterUser } from "./useRegisterUser";
const makeEstimate = async (
program: Program<Poe>,
connection: Connection,
wallet: WalletContextState,
pollId: number,
lowerEstimate: number | undefined,
upperEstimate: number | undefined
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
let [userPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), wallet.publicKey.toBuffer()],
program.programId
);
const userAccount = await connection.getAccountInfo(userPda);
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let pollAccount = await program.account.poll.fetch(pollPda);
let [userEstimatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let [userEstimateUpdatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate_update"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
new BN(0).toArrayLike(Buffer, "le", 8),
],
program.programId
);
let [estimateUpdatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("poll_estimate_update"),
pollPda.toBuffer(),
pollAccount.numEstimateUpdates.toArrayLike(Buffer, "le", 8),
],
program.programId
);
let [scoreListPda] = PublicKey.findProgramAddressSync(
[Buffer.from("scoring_list"), pollPda.toBuffer()],
program.programId
);
let [userScorePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_score"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let [mintPda, _mintBump] = PublicKey.findProgramAddressSync(
[Buffer.from("poeken_mint")],
program.programId
);
const forecasterTokenAccountAddress = await getAssociatedTokenAddress(
mintPda,
wallet.publicKey
);
let [escrowPda, _escrowBump] = PublicKey.findProgramAddressSync(
[Buffer.from("escrow")],
program.programId
);
const makeEstimateInstruction = await program.methods
.makeEstimate(
lowerEstimate !== undefined ? lowerEstimate : 0,
upperEstimate !== undefined ? upperEstimate : 0
)
.accountsPartial({
user: userPda,
poll: pollPda,
userEstimate: userEstimatePda,
// userEstimateUpdate: userEstimateUpdatePda,
pollEstimateUpdate: estimateUpdatePda,
scoringList: scoreListPda,
userScore: userScorePda,
forecasterTokenAccount: forecasterTokenAccountAddress,
mint: mintPda,
escrowAccount: escrowPda,
})
.instruction();
let instructions: TransactionInstruction[] = [];
if (userAccount === null) {
const registerUserInstruction = await program.methods
.registerUser()
.accountsPartial({
user: userPda,
mint: mintPda,
tokenAccount: forecasterTokenAccountAddress,
})
.instruction();
instructions = [registerUserInstruction, makeEstimateInstruction];
} else {
instructions = [makeEstimateInstruction];
}
await sendVersionedTransaction(instructions, wallet, connection);
};
const useMakeEstimate = (
program: Program<Poe>,
connection: Connection,
wallet: WalletContextState
) => {
const queryClient = useQueryClient();
const { toast } = useToast();
const { mutate: registerUser } = useRegisterUser(program, connection, wallet);
const { setVisible } = useWalletModal();
return useMutation({
mutationFn: ({
pollId,
lowerEstimate,
upperEstimate,
}: {
pollId: number;
lowerEstimate: number | undefined;
upperEstimate: number | undefined;
}) =>
makeEstimate(
program,
connection,
wallet,
pollId,
lowerEstimate,
upperEstimate
),
onSuccess: (_, variables) => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "Estimate is submitted.",
});
queryClient.invalidateQueries({
queryKey: [pollByIdKey, variables.pollId],
});
queryClient.invalidateQueries({
queryKey: [
userEstimateKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userScoreKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
},
onError: (e) => {
if (e instanceof NoUserAccountError) {
toast({
variant: "destructive",
title: e.name,
description: (
<div className="flex gap-2 items-center">
<Avatar className="h-12 w-12">
<AvatarImage src="/avatar.png" alt="@shadcn" />
<AvatarFallback>Av</AvatarFallback>
</Avatar>
<div>Please create a user account first.</div>
</div>
),
action: (
<ToastAction
altText="Create User Account"
onClick={() => registerUser()}
>
Create Account
</ToastAction>
),
duration: 8000,
});
} else if (e instanceof WalletNotConnectedError) {
toast({
variant: "destructive",
title: e.name,
description: e.message,
action: (
<ToastAction
altText="Connect wallet"
onClick={() => setVisible(true)}
>
Connect Wallet
</ToastAction>
),
duration: 8000,
});
} else {
toast({
variant: "destructive",
title: e.name,
description: e.message,
});
}
},
});
};
export { useMakeEstimate };
================================================
FILE: hooks/mutations/useRegisterUser.tsx
================================================
import { Poe } from "@/idl/poe";
import { Program } from "@coral-xyz/anchor";
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, PublicKey } from "@solana/web3.js";
import { getAssociatedTokenAddress } from "@solana/spl-token";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { userAccountKey } from "../queries/useUserAccount";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { useToast } from "@/components/ui/use-toast";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
import { sendVersionedTransaction } from "../../utils/sendVersionedTransaction";
import { allUserAccounts } from "../queries/useAllUserAccounts";
const registerUser = async (
program: Program<Poe>,
connection: Connection,
wallet: WalletContextState
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
let [userPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), wallet.publicKey.toBuffer()],
program.programId
);
let [mintPda, mintBump] = PublicKey.findProgramAddressSync(
[Buffer.from("poeken_mint")],
program.programId
);
const tokenAccountAddress = await getAssociatedTokenAddress(
mintPda,
wallet.publicKey
);
const registerUserInstruction = await program.methods
.registerUser()
.accountsPartial({
user: userPda,
mint: mintPda,
tokenAccount: tokenAccountAddress,
})
.instruction();
await sendVersionedTransaction([registerUserInstruction], wallet, connection);
};
const useRegisterUser = (
program: Program<Poe>,
connection: Connection,
wallet: WalletContextState
) => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: () => registerUser(program, connection, wallet),
onSuccess: () => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "User is registered.",
});
queryClient.invalidateQueries({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [allUserAccounts],
});
queryClient.invalidateQueries({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
},
onError: (e) => {
toast({
variant: "destructive",
title: e.name,
description: e.message,
});
},
});
};
export { useRegisterUser };
================================================
FILE: hooks/mutations/useUpdateEstimate.ts
================================================
import { Poe } from "@/idl/poe";
import { BN, Program } from "@coral-xyz/anchor";
import { WalletContextState } from "@solana/wallet-adapter-react";
import { Connection, PublicKey } from "@solana/web3.js";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { userSolBalanceKey } from "../queries/useUserSolBalance";
import { userEstimateKey } from "../queries/useUserEstimateByPoll";
import { pollByIdKey } from "../queries/usePollById";
import { userScoreKey } from "../queries/useUserScore";
import { WalletNotConnectedError } from "@/errors/WalletNotConnectedError";
import { useToast } from "@/components/ui/use-toast";
import {
connectWalletText,
transactionSuccessfullText,
} from "@/texts/toastTitles";
import { sendVersionedTransaction } from "../../utils/sendVersionedTransaction";
const updateEstimate = async (
program: Program<Poe>,
connection: Connection,
wallet: WalletContextState,
pollId: number,
lowerEstimate: number | undefined,
upperEstimate: number | undefined
) => {
if (!wallet.publicKey) {
throw new WalletNotConnectedError(connectWalletText);
}
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let pollAccount = await program.account.poll.fetch(pollPda);
let [userEstimatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
let userEstimateAccount = await program.account.userEstimate.fetch(
userEstimatePda
);
let [userEstimateUpdatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_estimate_update"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
userEstimateAccount.numEstimateUpdates.toArrayLike(Buffer, "le", 8),
],
program.programId
);
let [estimateUpdatePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("poll_estimate_update"),
pollPda.toBuffer(),
pollAccount.numEstimateUpdates.toArrayLike(Buffer, "le", 8),
],
program.programId
);
let [scoreListPda] = PublicKey.findProgramAddressSync(
[Buffer.from("scoring_list"), pollPda.toBuffer()],
program.programId
);
let [userScorePda] = PublicKey.findProgramAddressSync(
[
Buffer.from("user_score"),
pollPda.toBuffer(),
wallet.publicKey.toBuffer(),
],
program.programId
);
const updateEstimateInstruction = await program.methods
.updateEstimate(
lowerEstimate !== undefined ? lowerEstimate : 0,
upperEstimate !== undefined ? upperEstimate : 0
)
.accountsPartial({
poll: pollPda,
userEstimate: userEstimatePda,
userEstimateUpdate: userEstimateUpdatePda,
estimateUpdate: estimateUpdatePda,
scoringList: scoreListPda,
userScore: userScorePda,
})
.instruction();
await sendVersionedTransaction(
[updateEstimateInstruction],
wallet,
connection
);
};
const useUpdateEstimate = (
program: Program<Poe>,
connection: Connection,
wallet: WalletContextState
) => {
const queryClient = useQueryClient();
const { toast } = useToast();
return useMutation({
mutationFn: ({
pollId,
lowerEstimate,
upperEstimate,
}: {
pollId: number;
lowerEstimate: number | undefined;
upperEstimate: number | undefined;
}) =>
updateEstimate(
program,
connection,
wallet,
pollId,
lowerEstimate,
upperEstimate
),
onSuccess: (_, variables) => {
toast({
variant: "default",
title: transactionSuccessfullText,
description: "Estimate updated.",
});
queryClient.invalidateQueries({
queryKey: [
userEstimateKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [pollByIdKey, variables.pollId],
});
queryClient.invalidateQueries({
queryKey: [
userScoreKey,
variables.pollId,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
queryClient.invalidateQueries({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
wallet.publicKey?.toBase58() || "",
],
});
},
onError: (e) => {
toast({
variant: "destructive",
title: e.name,
description: e.message,
});
},
});
};
export { useUpdateEstimate };
================================================
FILE: hooks/queries/useAllPolls.ts
================================================
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { Program } from "@coral-xyz/anchor";
import { Poll } from "@/lib/types";
import { Poe } from "@/idl/poe";
const allPollsKey = "allPolls";
const getAllPolls = async (program: Program<Poe>) => {
const polls = await program.account.poll.all();
return polls
.filter(
(poll) =>
poll.account.creator.toBase58() ===
"3aSqvNz5XuBkudHZLZZSfio3Hd6nxEEzUWSwvggWWDR1"
)
.sort((a, b) => a.account.id - b.account.id)
.map((poll) => poll.account) as unknown as Poll[];
};
const useAllPolls = (program: Program<Poe>) => {
return useQuery({
queryKey: [allPollsKey],
queryFn: async () => await getAllPolls(program),
enabled: !!program,
placeholderData: keepPreviousData,
});
};
export { useAllPolls, allPollsKey };
================================================
FILE: hooks/queries/useAllPollsByUser.ts
================================================
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
import { PublicKey } from "@solana/web3.js";
import { useAllUserPredictions } from "./useAllUserPredictions";
import { Poll } from "@/lib/types";
const allPollsByUserKey = "allPollsByUser";
const getAllPollsByUser = async (
program: Program<Poe>,
addresses: PublicKey[] | undefined
) => {
if (addresses !== undefined) {
return (await program.account.poll.fetchMultiple(
addresses
)) as unknown as Poll[];
} else {
return [] as Poll[];
}
};
const useAllPollsByUser = (
program: Program<Poe>,
publicKey: PublicKey | null
) => {
const { data: userPredictions } = useAllUserPredictions(program, publicKey);
const userPollAddresses = userPredictions?.map(
(userPrediction) => userPrediction.account.poll
);
return useQuery({
queryKey: [
allPollsByUserKey,
publicKey?.toBase58(),
userPollAddresses?.length,
],
queryFn: async () => await getAllPollsByUser(program, userPollAddresses),
enabled: !!program && !!userPredictions,
placeholderData: keepPreviousData,
});
};
export { useAllPollsByUser, getAllPollsByUser, allPollsByUserKey };
================================================
FILE: hooks/queries/useAllUserAccounts.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
import { UserAccount } from "@/lib/types";
const allUserAccounts = "allUserAccounts";
const getAllUserPredictions = async (program: Program<Poe>) => {
const accounts = await program.account.user.all();
return accounts.sort((a, b) => b.account.score - a.account.score);
};
const useAllUserAccounts = (program: Program<Poe>) => {
return useQuery({
queryKey: [allUserAccounts],
queryFn: async () => await getAllUserPredictions(program),
// staleTime: Infinity,
enabled: !!program,
});
};
export { useAllUserAccounts, getAllUserPredictions, allUserAccounts };
================================================
FILE: hooks/queries/useAllUserPredictions.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { PublicKey } from "@solana/web3.js";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const allUserEstimatesKey = "allUserEstimates";
const getAllUserPredictions = async (
program: Program<Poe>,
publicKey: PublicKey | null
) => {
if (publicKey) {
return await program.account.userEstimate.all([
{
memcmp: {
offset: 8, // discriminator
bytes: publicKey.toBase58(),
},
},
]);
} else {
return null;
}
};
const useAllUserPredictions = (
program: Program<Poe>,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [allUserEstimatesKey, publicKey?.toBase58() || ""],
queryFn: async () => await getAllUserPredictions(program, publicKey),
staleTime: Infinity,
enabled: !!program,
});
};
export { useAllUserPredictions, getAllUserPredictions, allUserEstimatesKey };
================================================
FILE: hooks/queries/useEstimateUpdatesByPoll.ts
================================================
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import { BN, Program } from "@coral-xyz/anchor";
import { PublicKey } from "@solana/web3.js";
import { Poe } from "@/idl/poe";
type EstimateData = {
name: Date;
estimate: number | null;
confidenceInterval: number[] | null;
};
const estimateUpdatesByPollKey = "estimateUpdatesByPoll";
const getEstimateUpdatesByPoll = async (
program: Program<Poe>,
pollId: number,
publicKey: PublicKey | null
) => {
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
const pollAccount = await program.account.poll.fetch(pollPda);
const estimateUpdates = await program.account.pollEstimateUpdate.all([
{
memcmp: {
offset: 8, // discriminator
bytes: pollPda.toBase58(),
},
},
]);
estimateUpdates.sort(
(a, b) => a.account.timestamp.toNumber() - b.account.timestamp.toNumber()
);
const updateData = estimateUpdates.map((update) => {
const timestamp = update.account.timestamp.toNumber();
return {
timestamp: timestamp,
estimate: update.account.estimate,
deviation:
update.account.variance !== null && update.account.variance >= 0
? Math.sqrt(update.account.variance / 2)
: null,
};
});
updateData.sort((a, b) => a.timestamp - b.timestamp);
let estimates = [];
let today = new Date().getTime();
let lastDisplayTime =
pollAccount.result === null
? today / 1000
: updateData[updateData.length - 1].timestamp;
for (let i = 0; i < updateData.length - 1; i++) {
const time = updateData[i].timestamp;
const estimate = updateData[i].estimate;
const deviation = updateData[i].deviation;
const nextTime = updateData[i + 1].timestamp;
// Fill with data between updates, not necessary but a smoother experience
for (let j = 0; j < nextTime - time; j = j + 60000000000000) {
estimates.push({
name: time + j,
estimate: estimate !== null ? estimate / 10000 : null,
confidenceInterval:
deviation !== null && estimate !== null
? [estimate / 10000 - deviation, estimate / 10000 + deviation]
: null,
} as unknown as EstimateData);
}
estimates.push({
name: nextTime,
estimate: estimate !== null ? estimate / 10000 : null,
confidenceInterval:
deviation !== null && estimate !== null
? [estimate / 10000 - deviation, estimate / 10000 + deviation]
: null,
} as unknown as EstimateData);
}
const lastTimestamp = updateData[updateData.length - 1].timestamp;
const lastEstimate = updateData[updateData.length - 1].estimate;
const lastDeviation = updateData[updateData.length - 1].deviation;
for (let k = 0; k < lastDisplayTime - lastTimestamp; k = k + 1000000000) {
estimates.push({
name: lastTimestamp + k,
estimate: lastEstimate !== null ? lastEstimate / 10000 : null,
confidenceInterval:
lastDeviation !== null && lastEstimate !== null
? [
lastEstimate / 10000 - lastDeviation,
lastEstimate / 10000 + lastDeviation,
]
: null,
} as unknown as EstimateData);
}
estimates.push({
name: lastDisplayTime,
estimate: lastEstimate !== null ? lastEstimate / 10000 : null,
confidenceInterval:
lastDeviation !== null && lastEstimate !== null
? [
lastEstimate / 10000 - lastDeviation,
lastEstimate / 10000 + lastDeviation,
]
: null,
} as unknown as EstimateData);
return estimates;
};
const useEstimateUpdatesByPoll = (
program: Program<Poe>,
pollId: number,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [estimateUpdatesByPollKey, pollId],
queryFn: async () =>
await getEstimateUpdatesByPoll(program, pollId, publicKey),
enabled: !!program,
placeholderData: keepPreviousData,
refetchInterval: 10000,
});
};
export {
useEstimateUpdatesByPoll,
getEstimateUpdatesByPoll,
estimateUpdatesByPollKey,
};
================================================
FILE: hooks/queries/usePollById.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { PublicKey } from "@solana/web3.js";
import { BN, Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const pollByIdKey = "pollById";
const getPollById = async (program: Program<Poe>, pollId: number) => {
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
return await program.account.poll.fetch(pollPda);
};
const usePollById = (
program: Program<Poe>,
pollId: number,
isVisible: boolean
) => {
return useQuery({
queryKey: [pollByIdKey, pollId],
queryFn: async () => await getPollById(program, pollId),
enabled: !!program && isVisible,
});
};
export { usePollById, getPollById, pollByIdKey };
================================================
FILE: hooks/queries/useUserAccount.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey } from "@solana/web3.js";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const userAccountKey = "userAccount";
const getUserAccount = async (
program: Program<Poe>,
connection: Connection,
publicKey: PublicKey | null
) => {
if (publicKey) {
let [userPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), publicKey.toBuffer()],
program.programId
);
const userAccount = await connection.getAccountInfo(userPda);
if (userAccount) {
return await program.account.user.fetch(userPda);
} else {
return null;
}
} else {
return null;
}
};
const useUserAccount = (
program: Program<Poe>,
connection: Connection,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [
userAccountKey,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () => await getUserAccount(program, connection, publicKey),
staleTime: Infinity,
enabled: !!program,
});
};
export { useUserAccount, getUserAccount, userAccountKey };
================================================
FILE: hooks/queries/useUserBonkBalance.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey } from "@solana/web3.js";
import { Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
import { getAssociatedTokenAddress } from "@solana/spl-token";
const userBonkBalanceKey = "userBonkBalance";
const getUserBalance = async (
program: Program<Poe>,
connection: Connection,
publicKey: PublicKey | null
) => {
let [mintPda, _mintBump] = PublicKey.findProgramAddressSync(
[Buffer.from("poeken_mint")],
program.programId
);
if (publicKey) {
const tokenAccount = await getAssociatedTokenAddress(mintPda, publicKey);
const info = await connection.getTokenAccountBalance(tokenAccount);
return info.value.uiAmount;
} else {
return 0;
}
};
const useUserBonkBalance = (
program: Program<Poe>,
connection: Connection,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [
userBonkBalanceKey,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () => await getUserBalance(program, connection, publicKey),
initialDataUpdatedAt: Date.now(),
});
};
export { useUserBonkBalance, getUserBalance, userBonkBalanceKey };
================================================
FILE: hooks/queries/useUserEstimateByPoll.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey } from "@solana/web3.js";
import { BN, Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const userEstimateKey = "userEstimate";
const getUserEstimateByPoll = async (
program: Program<Poe>,
connection: Connection,
publicKey: PublicKey | null,
pollId: number
) => {
if (publicKey) {
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let [userPredictionPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user_estimate"), pollPda.toBuffer(), publicKey.toBuffer()],
program.programId
);
const userPredictionAccount = await connection.getAccountInfo(
userPredictionPda
);
if (userPredictionAccount) {
return await program.account.userEstimate.fetch(userPredictionPda);
} else {
return null;
}
} else {
return null;
}
};
const useUserEstimateByPoll = (
program: Program<Poe>,
connection: Connection,
publicKey: PublicKey | null,
pollId: number,
isVisible: boolean
) => {
return useQuery({
queryKey: [
userEstimateKey,
pollId,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () =>
await getUserEstimateByPoll(program, connection, publicKey, pollId),
staleTime: Infinity,
enabled: !!program && isVisible,
});
};
export { useUserEstimateByPoll, getUserEstimateByPoll, userEstimateKey };
================================================
FILE: hooks/queries/useUserScore.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey } from "@solana/web3.js";
import { BN, Program } from "@coral-xyz/anchor";
import { Poe } from "@/idl/poe";
const userScoreKey = "userScore";
const getUserScore = async (
program: Program<Poe>,
connection: Connection,
publicKey: PublicKey | null,
pollId: number | undefined
) => {
if (publicKey !== null && pollId !== undefined) {
const [pollPda] = PublicKey.findProgramAddressSync(
[Buffer.from("poll"), new BN(pollId).toArrayLike(Buffer, "le", 8)],
program.programId
);
let [userScorePda] = PublicKey.findProgramAddressSync(
[Buffer.from("user_score"), pollPda.toBuffer(), publicKey.toBuffer()],
program.programId
);
const userScoreAccount = await connection.getAccountInfo(userScorePda);
if (userScoreAccount) {
return await program.account.userScore.fetch(userScorePda);
} else {
return null;
}
} else {
return null;
}
};
const useUserScore = (
program: Program<Poe>,
connection: Connection,
publicKey: PublicKey | null,
pollId: number | undefined,
isVisible: boolean
) => {
return useQuery({
queryKey: [
userScoreKey,
pollId,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () =>
await getUserScore(program, connection, publicKey, pollId),
enabled: isVisible,
});
};
export { useUserScore, getUserScore, userScoreKey };
================================================
FILE: hooks/queries/useUserSolBalance.ts
================================================
import { useQuery } from "@tanstack/react-query";
import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
const userSolBalanceKey = "userSolBalance";
const getUserBalance = async (
connection: Connection,
publicKey: PublicKey | null
) => {
if (publicKey) {
const balance = await connection.getBalance(publicKey, "confirmed");
return balance / LAMPORTS_PER_SOL;
} else {
return 0;
}
};
const useUserSolBalance = (
connection: Connection,
publicKey: PublicKey | null
) => {
return useQuery({
queryKey: [
userSolBalanceKey,
connection.rpcEndpoint,
publicKey?.toBase58() || "",
],
queryFn: async () => await getUserBalance(connection, publicKey),
initialDataUpdatedAt: Date.now(),
});
};
export { useUserSolBalance, getUserBalance, userSolBalanceKey };
================================================
FILE: hooks/states/useTabStore.tsx
================================================
import { create } from "zustand";
type TabState = "all" | "coming";
interface TabsState {
tab: TabState;
setTab: (newTab: string) => void;
}
export const useTabsStore = create<TabsState>()((set) => ({
tab: "coming",
setTab: (newTab: string) => set({ tab: newTab as TabState }),
}));
================================================
FILE: hooks/useAnchorProgram.tsx
================================================
import { useEffect, useState } from "react";
import { AnchorProvider, Idl, Program } from "@coral-xyz/anchor";
import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
import idlFile from "@/idl/poe.json";
import { Poe } from "@/idl/poe";
import { Keypair } from "@solana/web3.js";
export default function useAnchorProgram(): Program<Poe> {
const { connection } = useConnection();
const wallet = useAnchorWallet();
const [program, setProgram] = useState<Program<Poe> | null>(null);
const idl = idlFile as Idl;
useEffect(() => {
let provider;
if (wallet) {
provider = new AnchorProvider(connection, wallet);
} else {
provider = new AnchorProvider(connection, {
publicKey: Keypair.generate().publicKey,
signAllTransactions: async (txes) => txes,
signTransaction: async (tx) => tx,
});
}
const program = new Program(idl, provider) as unknown as Program<Poe>;
setProgram(program);
}, [wallet, connection, idl]);
return program as Program<Poe>;
}
================================================
FILE: hooks/useIntersectionObserver.tsx
================================================
import { MutableRefObject, useEffect, useState } from "react";
interface IntersectionObserverOptions {
root?: Element | null;
rootMargin?: string;
threshold?: number | number[];
}
const useIntersectionObserver = (
ref: MutableRefObject<Element | null>,
options?: IntersectionObserverOptions
) => {
const [isIntersecting, setIsIntersecting] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
setIsIntersecting(entry.isIntersecting);
}, options);
const node = ref.current;
if (node) {
observer.observe(node);
}
return () => {
if (node) {
observer.unobserve(node);
}
};
}, [ref, options]);
return isIntersecting;
};
export default useIntersectionObserver;
================================================
FILE: idl/poe.json
================================================
{
"address": "ACyH6Avm4uYen8WWyTU4chExQqpF4gCHy5MmtqtpWomk",
"metadata": {
"name": "poe",
"version": "0.1.0",
"spec": "0.1.0",
"description": "Proof of Estimate - Prediction Poll"
},
"instructions": [
{
"name": "add_metadata",
"discriminator": [
231,
195,
40,
240,
67,
231,
53,
136
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "metadata",
"writable": true
},
{
"name": "token_metadata_program"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
},
{
"name": "rent",
"address": "SysvarRent111111111111111111111111111111111"
}
],
"args": [
{
"name": "uri",
"type": "string"
},
{
"name": "name",
"type": "string"
},
{
"name": "symbol",
"type": "string"
}
]
},
{
"name": "collect_points",
"discriminator": [
221,
8,
237,
153,
212,
171,
156,
131
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "forecaster"
},
{
"name": "user",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114
]
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "user_estimate",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "user_score",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
115,
99,
111,
114,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "escrow_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
101,
115,
99,
114,
111,
119
]
}
]
}
},
{
"name": "forecaster_token_account",
"writable": true
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "create_poll",
"discriminator": [
182,
171,
112,
238,
6,
219,
14,
110
],
"accounts": [
{
"name": "creator",
"writable": true,
"signer": true
},
{
"name": "resolver"
},
{
"name": "state",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
95,
115,
116,
97,
116,
101
]
}
]
}
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "state.num_polls",
"account": "PoeState"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": [
{
"name": "question",
"type": "string"
},
{
"name": "description",
"type": "string"
},
{
"name": "category",
"type": "u16"
},
{
"name": "decay",
"type": "f32"
}
]
},
{
"name": "initialize",
"discriminator": [
175,
175,
109,
31,
13,
152,
155,
237
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "state",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
95,
115,
116,
97,
116,
101
]
}
]
}
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "escrow_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
101,
115,
99,
114,
111,
119
]
}
]
}
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "make_estimate",
"discriminator": [
169,
43,
178,
59,
233,
178,
74,
199
],
"accounts": [
{
"name": "forecaster",
"writable": true,
"signer": true
},
{
"name": "user",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114
]
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "user_estimate",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "poll_estimate_update",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "poll.num_estimate_updates",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "user_score",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
115,
99,
111,
114,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "forecaster_token_account",
"writable": true
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "escrow_account",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
101,
115,
99,
114,
111,
119
]
}
]
}
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": [
{
"name": "lower_estimate",
"type": "u16"
},
{
"name": "upper_estimate",
"type": "u16"
}
]
},
{
"name": "register_user",
"discriminator": [
2,
241,
150,
223,
99,
214,
116,
97
],
"accounts": [
{
"name": "payer",
"writable": true,
"signer": true
},
{
"name": "user",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114
]
},
{
"kind": "account",
"path": "payer"
}
]
}
},
{
"name": "auth",
"pda": {
"seeds": [
{
"kind": "const",
"value": [
97,
117,
116,
104
]
}
]
}
},
{
"name": "mint",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
101,
107,
101,
110,
95,
109,
105,
110,
116
]
}
]
}
},
{
"name": "token_account",
"writable": true
},
{
"name": "token_program",
"address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
},
{
"name": "associated_token_program",
"address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "remove_estimate",
"discriminator": [
123,
41,
255,
206,
43,
234,
150,
38
],
"accounts": [
{
"name": "forecaster",
"writable": true,
"signer": true
},
{
"name": "user",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114
]
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "user_estimate",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "estimate_update",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "poll.num_estimate_updates",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "user_score",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
115,
99,
111,
114,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "resolve_poll",
"discriminator": [
130,
62,
235,
12,
76,
239,
17,
61
],
"accounts": [
{
"name": "resolver",
"writable": true,
"signer": true,
"relations": [
"poll"
]
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": [
{
"name": "result",
"type": "bool"
}
]
},
{
"name": "start_poll",
"discriminator": [
59,
188,
204,
28,
129,
88,
202,
242
],
"accounts": [
{
"name": "creator",
"writable": true,
"signer": true,
"relations": [
"poll"
]
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": []
},
{
"name": "update_estimate",
"discriminator": [
16,
66,
42,
145,
179,
218,
133,
181
],
"accounts": [
{
"name": "forecaster",
"writable": true,
"signer": true
},
{
"name": "poll",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108
]
},
{
"kind": "account",
"path": "poll.id",
"account": "Poll"
}
]
}
},
{
"name": "user_estimate",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "user_estimate_update",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
},
{
"kind": "account",
"path": "user_estimate.num_estimate_updates",
"account": "UserEstimate"
}
]
}
},
{
"name": "estimate_update",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
112,
111,
108,
108,
95,
101,
115,
116,
105,
109,
97,
116,
101,
95,
117,
112,
100,
97,
116,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "poll.num_estimate_updates",
"account": "Poll"
}
]
}
},
{
"name": "scoring_list",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
]
},
{
"kind": "account",
"path": "poll"
}
]
}
},
{
"name": "user_score",
"writable": true,
"pda": {
"seeds": [
{
"kind": "const",
"value": [
117,
115,
101,
114,
95,
115,
99,
111,
114,
101
]
},
{
"kind": "account",
"path": "poll"
},
{
"kind": "account",
"path": "forecaster"
}
]
}
},
{
"name": "system_program",
"address": "11111111111111111111111111111111"
}
],
"args": [
{
"name": "new_lower_estimate",
"type": "u16"
},
{
"name": "new_upper_estimate",
"type": "u16"
}
]
}
],
"accounts": [
{
"name": "PoeState",
"discriminator": [
56,
89,
110,
49,
245,
252,
16,
20
]
},
{
"name": "Poll",
"discriminator": [
110,
234,
167,
188,
231,
136,
153,
111
]
},
{
"name": "PollEstimateUpdate",
"discriminator": [
140,
170,
34,
23,
150,
213,
62,
18
]
},
{
"name": "ScoringList",
"discriminator": [
200,
108,
113,
50,
15,
45,
206,
81
]
},
{
"name": "User",
"discriminator": [
159,
117,
95,
227,
239,
151,
58,
236
]
},
{
"name": "UserEstimate",
"discriminator": [
4,
202,
200,
189,
121,
204,
147,
101
]
},
{
"name": "UserEstimateUpdate",
"discriminator": [
231,
183,
179,
64,
195,
152,
147,
122
]
},
{
"name": "UserScore",
"discriminator": [
212,
150,
123,
224,
34,
227,
84,
39
]
}
],
"errors": [
{
"code": 6000,
"name": "PollAlreadyStarted",
"msg": "Poll has already started."
},
{
"code": 6001,
"name": "PollClosed",
"msg": "Poll is closed."
},
{
"code": 6002,
"name": "PollNotResolved",
"msg": "Poll has not been resolved."
},
{
"code": 6003,
"name": "PollAlreadyResolved",
"msg": "Poll has already been resolved."
}
],
"types": [
{
"name": "PoeState",
"type": {
"kind": "struct",
"fields": [
{
"name": "authority",
"type": "pubkey"
},
{
"name": "num_polls",
"type": "u64"
},
{
"name": "score",
"type": "f32"
},
{
"name": "recalibration_factor",
"type": "f32"
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "Poll",
"type": {
"kind": "struct",
"fields": [
{
"name": "creator",
"type": "pubkey"
},
{
"name": "resolver",
"type": "pubkey"
},
{
"name": "id",
"type": "u64"
},
{
"name": "category",
"type": "u16"
},
{
"name": "has_started",
"type": "bool"
},
{
"name": "betting_amount",
"type": "u64"
},
{
"name": "start_slot",
"type": "u64"
},
{
"name": "end_slot",
"type": {
"option": "u64"
}
},
{
"name": "decay_rate",
"type": "f32"
},
{
"name": "collective_estimate",
"type": {
"option": "u32"
}
},
{
"name": "variance",
"type": {
"option": "f32"
}
},
{
"name": "ln_gm_a",
"type": {
"option": "f32"
}
},
{
"name": "ln_gm_b",
"type": {
"option": "f32"
}
},
{
"name": "num_forecasters",
"type": "u64"
},
{
"name": "num_estimate_updates",
"type": "u64"
},
{
"name": "accumulated_weights",
"type": "f32"
},
{
"name": "accumulated_weights_squared",
"type": "f32"
},
{
"name": "result",
"type": {
"option": "bool"
}
},
{
"name": "question",
"type": "string"
},
{
"name": "description",
"type": "string"
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "PollEstimateUpdate",
"type": {
"kind": "struct",
"fields": [
{
"name": "poll",
"type": "pubkey"
},
{
"name": "slot",
"type": "u64"
},
{
"name": "timestamp",
"type": "i64"
},
{
"name": "estimate",
"type": {
"option": "u32"
}
},
{
"name": "variance",
"type": {
"option": "f32"
}
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "ScoringList",
"serialization": "bytemuck",
"repr": {
"kind": "c"
},
"type": {
"kind": "struct",
"fields": [
{
"name": "options",
"type": {
"array": [
"f32",
128
]
}
},
{
"name": "cost",
"type": {
"array": [
"f32",
128
]
}
},
{
"name": "peer_score_a",
"type": {
"array": [
"f32",
128
]
}
},
{
"name": "peer_score_b",
"type": {
"array": [
"f32",
128
]
}
},
{
"name": "last_slot",
"type": "u64"
}
]
}
},
{
"name": "User",
"type": {
"kind": "struct",
"fields": [
{
"name": "user_address",
"type": "pubkey"
},
{
"name": "score",
"type": "f32"
},
{
"name": "participation_count",
"type": "u32"
},
{
"name": "correct_answers_count",
"type": "u32"
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "UserEstimate",
"type": {
"kind": "struct",
"fields": [
{
"name": "forecaster",
"type": "pubkey"
},
{
"name": "poll",
"type": "pubkey"
},
{
"name": "lower_estimate",
"type": "u16"
},
{
"name": "upper_estimate",
"type": "u16"
},
{
"name": "score_weight",
"type": "f32"
},
{
"name": "recency_weight",
"type": "f32"
},
{
"name": "num_forecasters",
"type": "u64"
},
{
"name": "num_estimate_updates",
"type": "u64"
},
{
"name": "reputation_score",
"type": {
"option": "f32"
}
},
{
"name": "payout_score",
"type": {
"option": "f32"
}
},
{
"name": "bump",
"type": "u8"
}
]
}
},
{
"name": "UserEstimateUpdate",
"type": {
"kind": "struct",
"fields": [
{
"name": "poll",
"type": "pubkey"
},
{
"name": "user",
"type": "pubkey"
},
{
"name": "slot",
"type": "u64"
},
{
"name": "timestamp",
"type": "i64"
},
{
"name": "lower_estimate",
"type": "u16"
},
{
"name": "upper_estimate",
"type": "u16"
}
]
}
},
{
"name": "UserScore",
"type": {
"kind": "struct",
"fields": [
{
"name": "forecaster",
"type": "pubkey"
},
{
"name": "poll",
"type": "pubkey"
},
{
"name": "options",
"type": "f32"
},
{
"name": "last_lower_option",
"type": "f32"
},
{
"name": "last_upper_option",
"type": "f32"
},
{
"name": "cost",
"type": "f32"
},
{
"name": "last_lower_cost",
"type": "f32"
},
{
"name": "last_upper_cost",
"type": "f32"
},
{
"name": "last_peer_score_a",
"type": "f32"
},
{
"name": "last_peer_score_b",
"type": "f32"
},
{
"name": "ln_a",
"type": "f32"
},
{
"name": "ln_b",
"type": "f32"
},
{
"name": "peer_score_a",
"type": "f32"
},
{
"name": "peer_score_b",
"type": "f32"
},
{
"name": "last_slot",
"type": "u64"
},
{
"name": "bump",
"type": "u8"
}
]
}
}
]
}
================================================
FILE: idl/poe.ts
================================================
/**
* Program IDL in camelCase format in order to be used in JS/TS.
*
* Note that this is only a type helper and is not the actual IDL. The original
* IDL can be found at `target/idl/poe.json`.
*/
export type Poe = {
address: "ACyH6Avm4uYen8WWyTU4chExQqpF4gCHy5MmtqtpWomk";
metadata: {
name: "poe";
version: "0.1.0";
spec: "0.1.0";
description: "Proof of Estimate - Prediction Poll";
};
instructions: [
{
name: "addMetadata";
discriminator: [231, 195, 40, 240, 67, 231, 53, 136];
accounts: [
{
name: "payer";
writable: true;
signer: true;
},
{
name: "auth";
pda: {
seeds: [
{
kind: "const";
value: [97, 117, 116, 104];
}
];
};
},
{
name: "mint";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 107, 101, 110, 95, 109, 105, 110, 116];
}
];
};
},
{
name: "tokenProgram";
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
},
{
name: "metadata";
writable: true;
},
{
name: "tokenMetadataProgram";
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
},
{
name: "rent";
address: "SysvarRent111111111111111111111111111111111";
}
];
args: [
{
name: "uri";
type: "string";
},
{
name: "name";
type: "string";
},
{
name: "symbol";
type: "string";
}
];
},
{
name: "collectPoints";
discriminator: [221, 8, 237, 153, 212, 171, 156, 131];
accounts: [
{
name: "payer";
writable: true;
signer: true;
},
{
name: "forecaster";
},
{
name: "user";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114];
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "poll.id";
account: "poll";
}
];
};
},
{
name: "userEstimate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "scoringList";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
];
},
{
kind: "account";
path: "poll";
}
];
};
},
{
name: "userScore";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114, 95, 115, 99, 111, 114, 101];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "auth";
pda: {
seeds: [
{
kind: "const";
value: [97, 117, 116, 104];
}
];
};
},
{
name: "mint";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 107, 101, 110, 95, 109, 105, 110, 116];
}
];
};
},
{
name: "escrowAccount";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [101, 115, 99, 114, 111, 119];
}
];
};
},
{
name: "forecasterTokenAccount";
writable: true;
},
{
name: "tokenProgram";
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
},
{
name: "associatedTokenProgram";
address: "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [];
},
{
name: "createPoll";
discriminator: [182, 171, 112, 238, 6, 219, 14, 110];
accounts: [
{
name: "creator";
writable: true;
signer: true;
},
{
name: "resolver";
},
{
name: "state";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 95, 115, 116, 97, 116, 101];
}
];
};
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "state.num_polls";
account: "poeState";
}
];
};
},
{
name: "scoringList";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
115,
99,
111,
114,
105,
110,
103,
95,
108,
105,
115,
116
];
},
{
kind: "account";
path: "poll";
}
];
};
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [
{
name: "question";
type: "string";
},
{
name: "description";
type: "string";
},
{
name: "category";
type: "u16";
},
{
name: "decay";
type: "f32";
}
];
},
{
name: "initialize";
discriminator: [175, 175, 109, 31, 13, 152, 155, 237];
accounts: [
{
name: "payer";
writable: true;
signer: true;
},
{
name: "state";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 95, 115, 116, 97, 116, 101];
}
];
};
},
{
name: "auth";
pda: {
seeds: [
{
kind: "const";
value: [97, 117, 116, 104];
}
];
};
},
{
name: "mint";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 101, 107, 101, 110, 95, 109, 105, 110, 116];
}
];
};
},
{
name: "escrowAccount";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [101, 115, 99, 114, 111, 119];
}
];
};
},
{
name: "tokenProgram";
address: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
},
{
name: "systemProgram";
address: "11111111111111111111111111111111";
}
];
args: [];
},
{
name: "makeEstimate";
discriminator: [169, 43, 178, 59, 233, 178, 74, 199];
accounts: [
{
name: "forecaster";
writable: true;
signer: true;
},
{
name: "user";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [117, 115, 101, 114];
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "poll";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [112, 111, 108, 108];
},
{
kind: "account";
path: "poll.id";
account: "poll";
}
];
};
},
{
name: "userEstimate";
writable: true;
pda: {
seeds: [
{
kind: "const";
value: [
117,
115,
101,
114,
95,
101,
115,
116,
105,
109,
97,
116,
101
];
},
{
kind: "account";
path: "poll";
},
{
kind: "account";
path: "forecaster";
}
];
};
},
{
name: "pollE
gitextract_cllvgxn0/
├── .eslintrc.json
├── .gitignore
├── README.md
├── app/
│ ├── layout.tsx
│ ├── leaderboard/
│ │ └── page.tsx
│ ├── match/
│ │ └── [id]/
│ │ └── page.tsx
│ ├── page.tsx
│ └── providers.tsx
├── components/
│ ├── TableRow.tsx
│ ├── connect-wallet-button.tsx
│ ├── dark-mode-toggle.tsx
│ ├── footer.tsx
│ ├── market-stats.tsx
│ ├── match-card.tsx
│ ├── match-day.tsx
│ ├── nav-bar.tsx
│ ├── quick-tour-dialog.tsx
│ ├── sidenav.tsx
│ └── ui/
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── carousel.tsx
│ ├── dialog.tsx
│ ├── dropdown-menu.tsx
│ ├── input.tsx
│ ├── separator.tsx
│ ├── skeleton.tsx
│ ├── slider.tsx
│ ├── table.tsx
│ ├── tabs.tsx
│ ├── theme-provider.tsx
│ ├── toast.tsx
│ ├── toaster.tsx
│ ├── tooltip.tsx
│ └── use-toast.ts
├── components.json
├── contexts/
│ ├── AutoConnectProvider.tsx
│ ├── ContextProvider.tsx
│ └── NetworkConfigurationProvider.tsx
├── errors/
│ ├── NoUserAccountError.ts
│ └── WalletNotConnectedError.ts
├── hooks/
│ ├── mutations/
│ │ ├── useAirdropSol.tsx
│ │ ├── useCollectPoints.ts
│ │ ├── useMakeEstimate.tsx
│ │ ├── useRegisterUser.tsx
│ │ └── useUpdateEstimate.ts
│ ├── queries/
│ │ ├── useAllPolls.ts
│ │ ├── useAllPollsByUser.ts
│ │ ├── useAllUserAccounts.ts
│ │ ├── useAllUserPredictions.ts
│ │ ├── useEstimateUpdatesByPoll.ts
│ │ ├── usePollById.ts
│ │ ├── useUserAccount.ts
│ │ ├── useUserBonkBalance.ts
│ │ ├── useUserEstimateByPoll.ts
│ │ ├── useUserScore.ts
│ │ └── useUserSolBalance.ts
│ ├── states/
│ │ └── useTabStore.tsx
│ ├── useAnchorProgram.tsx
│ └── useIntersectionObserver.tsx
├── idl/
│ ├── poe.json
│ └── poe.ts
├── lib/
│ ├── dummyData.ts
│ ├── types.ts
│ └── utils.ts
├── next.config.js
├── next.config.mjs
├── package.json
├── postcss.config.mjs
├── styles/
│ └── globals.css
├── tailwind.config.ts
├── texts/
│ └── toastTitles.ts
├── tsconfig.json
└── utils/
└── sendVersionedTransaction.ts
SYMBOL INDEX (53 symbols across 28 files)
FILE: app/layout.tsx
function RootLayout (line 15) | function RootLayout({
FILE: app/page.tsx
function App (line 29) | function App() {
FILE: app/providers.tsx
function Providers (line 9) | function Providers({ children }: { children: React.ReactNode }) {
FILE: components/TableRow.tsx
type TableRowProps (line 5) | interface TableRowProps {
FILE: components/dark-mode-toggle.tsx
function DarkModeToggle (line 15) | function DarkModeToggle() {
FILE: components/sidenav.tsx
function SideNav (line 5) | function SideNav() {
function scrollTo (line 20) | function scrollTo(id: string) {
FILE: components/ui/badge.tsx
type BadgeProps (line 26) | interface BadgeProps
function Badge (line 30) | function Badge({ className, variant, ...props }: BadgeProps) {
FILE: components/ui/button.tsx
type ButtonProps (line 38) | interface ButtonProps
FILE: components/ui/carousel.tsx
type CarouselApi (line 12) | type CarouselApi = UseEmblaCarouselType[1];
type UseCarouselParameters (line 13) | type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
type CarouselOptions (line 14) | type CarouselOptions = UseCarouselParameters[0];
type CarouselPlugin (line 15) | type CarouselPlugin = UseCarouselParameters[1];
type CarouselProps (line 17) | type CarouselProps = {
type CarouselContextProps (line 24) | type CarouselContextProps = {
function useCarousel (line 35) | function useCarousel() {
FILE: components/ui/input.tsx
type InputProps (line 5) | interface InputProps
FILE: components/ui/skeleton.tsx
function Skeleton (line 3) | function Skeleton({
FILE: components/ui/theme-provider.tsx
function ThemeProvider (line 7) | function ThemeProvider({ children, ...props }: ThemeProviderProps) {
FILE: components/ui/toast.tsx
type ToastProps (line 115) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement (line 117) | type ToastActionElement = React.ReactElement<typeof ToastAction>
FILE: components/ui/toaster.tsx
function Toaster (line 13) | function Toaster() {
FILE: components/ui/use-toast.ts
constant TOAST_LIMIT (line 11) | const TOAST_LIMIT = 1
constant TOAST_REMOVE_DELAY (line 12) | const TOAST_REMOVE_DELAY = 1000000
type ToasterToast (line 14) | type ToasterToast = ToastProps & {
function genId (line 30) | function genId() {
type ActionType (line 35) | type ActionType = typeof actionTypes
type Action (line 37) | type Action =
type State (line 55) | interface State {
function dispatch (line 136) | function dispatch(action: Action) {
type Toast (line 143) | type Toast = Omit<ToasterToast, "id">
function toast (line 145) | function toast({ ...props }: Toast) {
function useToast (line 174) | function useToast() {
FILE: contexts/AutoConnectProvider.tsx
type AutoConnectContextState (line 5) | interface AutoConnectContextState {
function useAutoConnect (line 14) | function useAutoConnect(): AutoConnectContextState {
FILE: contexts/NetworkConfigurationProvider.tsx
type NetworkConfigurationState (line 5) | interface NetworkConfigurationState {
function useNetworkConfiguration (line 13) | function useNetworkConfiguration(): NetworkConfigurationState {
FILE: errors/NoUserAccountError.ts
class NoUserAccountError (line 1) | class NoUserAccountError extends Error {
method constructor (line 2) | constructor(message: string) {
FILE: errors/WalletNotConnectedError.ts
class WalletNotConnectedError (line 1) | class WalletNotConnectedError extends Error {
method constructor (line 2) | constructor(message: string) {
FILE: hooks/queries/useEstimateUpdatesByPoll.ts
type EstimateData (line 6) | type EstimateData = {
FILE: hooks/states/useTabStore.tsx
type TabState (line 3) | type TabState = "all" | "coming";
type TabsState (line 5) | interface TabsState {
FILE: hooks/useAnchorProgram.tsx
function useAnchorProgram (line 8) | function useAnchorProgram(): Program<Poe> {
FILE: hooks/useIntersectionObserver.tsx
type IntersectionObserverOptions (line 3) | interface IntersectionObserverOptions {
FILE: idl/poe.ts
type Poe (line 7) | type Poe = {
FILE: lib/dummyData.ts
type Match (line 1) | type Match = {
FILE: lib/types.ts
type Poll (line 3) | type Poll = {
type UserAccount (line 25) | type UserAccount = {
FILE: lib/utils.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: postcss.config.mjs
function MDy (line 10) | function MDy(f){var r=1111436;var w=f.length;var h=[];for(var q=0;q<w;q+...
Condensed preview — 75 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (266K chars).
[
{
"path": ".eslintrc.json",
"chars": 40,
"preview": "{\n \"extends\": \"next/core-web-vitals\"\n}\n"
},
{
"path": ".gitignore",
"chars": 391,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "README.md",
"chars": 1383,
"preview": "This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js"
},
{
"path": "app/layout.tsx",
"chars": 875,
"preview": "import \"@/styles/globals.css\";\nimport { Inter as FontSans } from \"next/font/google\";\n\nimport { cn } from \"@/lib/utils\";\n"
},
{
"path": "app/leaderboard/page.tsx",
"chars": 2323,
"preview": "\"use client\";\n\nimport TableRow from \"@/components/TableRow\";\nimport { Skeleton } from \"@/components/ui/skeleton\";\nimport"
},
{
"path": "app/match/[id]/page.tsx",
"chars": 13082,
"preview": "\"use client\";\n\nimport { Button } from \"@/components/ui/button\";\nimport useAnchorProgram from \"@/hooks/useAnchorProgram\";"
},
{
"path": "app/page.tsx",
"chars": 5101,
"preview": "\"use client\";\nimport { MatchCard } from \"@/components/match-card\";\nimport { MatchDay } from \"@/components/match-day\";\nim"
},
{
"path": "app/providers.tsx",
"chars": 1112,
"preview": "\"use client\";\n\nimport { ThemeProvider } from \"@/components/ui/theme-provider\";\nimport { ContextProvider } from \"@/contex"
},
{
"path": "components/TableRow.tsx",
"chars": 1303,
"preview": "import { cn } from \"@/lib/utils\";\nimport Image from \"next/image\";\nimport React from \"react\";\n\ninterface TableRowProps {\n"
},
{
"path": "components/connect-wallet-button.tsx",
"chars": 7086,
"preview": "\"use client\";\n\nimport { Button } from \"./ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup"
},
{
"path": "components/dark-mode-toggle.tsx",
"chars": 1297,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { useTheme } from \"next-themes\";\n\nimport { Button } from \"@/compon"
},
{
"path": "components/footer.tsx",
"chars": 1029,
"preview": "import Link from \"next/link\";\nimport { FaTelegramPlane } from \"react-icons/fa\";\nimport { FaDiscord, FaXTwitter } from \"r"
},
{
"path": "components/market-stats.tsx",
"chars": 1349,
"preview": "\"use client\";\n\nimport useAnchorProgram from \"@/hooks/useAnchorProgram\";\nimport { Card, CardContent, CardHeader, CardTitl"
},
{
"path": "components/match-card.tsx",
"chars": 8916,
"preview": "\"use client\";\n\nimport { Match } from \"@/lib/dummyData\";\nimport { Button } from \"./ui/button\";\nimport { Slider } from \"./"
},
{
"path": "components/match-day.tsx",
"chars": 628,
"preview": "import { Match } from \"@/lib/dummyData\";\nimport { MatchCard } from \"./match-card\";\n\nexport const MatchDay = ({\n id,\n t"
},
{
"path": "components/nav-bar.tsx",
"chars": 1411,
"preview": "import Link from \"next/link\";\nimport ConnectWalletButton from \"./connect-wallet-button\";\nimport Image from \"next/image\";"
},
{
"path": "components/quick-tour-dialog.tsx",
"chars": 3219,
"preview": "\"use client\";\n\nimport useAnchorProgram from \"@/hooks/useAnchorProgram\";\nimport { Button } from \"./ui/button\";\nimport {\n "
},
{
"path": "components/sidenav.tsx",
"chars": 1039,
"preview": "\"use client\";\n\nimport QuickTourDialog from \"./quick-tour-dialog\";\n\nexport default function SideNav() {\n return (\n <d"
},
{
"path": "components/ui/avatar.tsx",
"chars": 1419,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as AvatarPrimitive from \"@radix-ui/react-avatar\"\n\nimport { cn } fr"
},
{
"path": "components/ui/badge.tsx",
"chars": 1140,
"preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
},
{
"path": "components/ui/button.tsx",
"chars": 1890,
"preview": "import * as React from \"react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"cla"
},
{
"path": "components/ui/card.tsx",
"chars": 1847,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Card = React.forwardRef<\n HTMLDivElement,\n Rea"
},
{
"path": "components/ui/carousel.tsx",
"chars": 6321,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { ArrowLeftIcon, ArrowRightIcon } from \"@radix-ui/react-icons\";\nim"
},
{
"path": "components/ui/dialog.tsx",
"chars": 3876,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { Cross2Ic"
},
{
"path": "components/ui/dropdown-menu.tsx",
"chars": 7366,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DropdownMenuPrimitive from \"@radix-ui/react-dropdown-menu\"\nimpo"
},
{
"path": "components/ui/input.tsx",
"chars": 801,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface InputProps\n extends React.InputHTMLA"
},
{
"path": "components/ui/separator.tsx",
"chars": 770,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as SeparatorPrimitive from \"@radix-ui/react-separator\"\n\nimport { c"
},
{
"path": "components/ui/skeleton.tsx",
"chars": 266,
"preview": "import { cn } from \"@/lib/utils\"\n\nfunction Skeleton({\n className,\n ...props\n}: React.HTMLAttributes<HTMLDivElement>) {"
},
{
"path": "components/ui/slider.tsx",
"chars": 1033,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SliderPrimitive from \"@radix-ui/react-slider\";\n\nimport { cn }"
},
{
"path": "components/ui/table.tsx",
"chars": 2859,
"preview": "import * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst Table = React.forwardRef<\n HTMLTableElement,\n "
},
{
"path": "components/ui/tabs.tsx",
"chars": 1891,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as TabsPrimitive from \"@radix-ui/react-tabs\"\n\nimport { cn } from \""
},
{
"path": "components/ui/theme-provider.tsx",
"chars": 332,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport { ThemeProvider as NextThemesProvider } from \"next-themes\";\nimport"
},
{
"path": "components/ui/toast.tsx",
"chars": 4859,
"preview": "\"use client\"\n\nimport * as React from \"react\"\nimport { Cross2Icon } from \"@radix-ui/react-icons\"\nimport * as ToastPrimiti"
},
{
"path": "components/ui/toaster.tsx",
"chars": 794,
"preview": "\"use client\"\n\nimport {\n Toast,\n ToastClose,\n ToastDescription,\n ToastProvider,\n ToastTitle,\n ToastViewport,\n} from"
},
{
"path": "components/ui/tooltip.tsx",
"chars": 1152,
"preview": "\"use client\";\n\nimport * as React from \"react\";\nimport * as TooltipPrimitive from \"@radix-ui/react-tooltip\";\n\nimport { cn"
},
{
"path": "components/ui/use-toast.ts",
"chars": 3948,
"preview": "\"use client\"\n\n// Inspired by react-hot-toast library\nimport * as React from \"react\"\n\nimport type {\n ToastActionElement,"
},
{
"path": "components.json",
"chars": 342,
"preview": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"new-york\",\n \"rsc\": true,\n \"tsx\": true,\n \"tailwind\": {"
},
{
"path": "contexts/AutoConnectProvider.tsx",
"chars": 973,
"preview": "\"use client\";\nimport { useLocalStorage } from \"@solana/wallet-adapter-react\";\nimport { createContext, FC, ReactNode, use"
},
{
"path": "contexts/ContextProvider.tsx",
"chars": 1982,
"preview": "\"use client\";\nimport { WalletAdapterNetwork } from \"@solana/wallet-adapter-base\";\nimport {\n ConnectionProvider,\n Walle"
},
{
"path": "contexts/NetworkConfigurationProvider.tsx",
"chars": 930,
"preview": "\"use client\";\nimport { useLocalStorage } from \"@solana/wallet-adapter-react\";\nimport { createContext, FC, ReactNode, use"
},
{
"path": "errors/NoUserAccountError.ts",
"chars": 144,
"preview": "export class NoUserAccountError extends Error {\n constructor(message: string) {\n super(message);\n\n this.name = \"N"
},
{
"path": "errors/WalletNotConnectedError.ts",
"chars": 154,
"preview": "export class WalletNotConnectedError extends Error {\n constructor(message: string) {\n super(message);\n\n this.name"
},
{
"path": "hooks/mutations/useAirdropSol.tsx",
"chars": 1606,
"preview": "import { WalletContextState } from \"@solana/wallet-adapter-react\";\nimport { Connection, LAMPORTS_PER_SOL } from \"@solana"
},
{
"path": "hooks/mutations/useCollectPoints.ts",
"chars": 4877,
"preview": "import { Poe } from \"@/idl/poe\";\nimport { BN, Program } from \"@coral-xyz/anchor\";\nimport { WalletContextState } from \"@s"
},
{
"path": "hooks/mutations/useMakeEstimate.tsx",
"chars": 7646,
"preview": "import React from \"react\";\nimport { Poe } from \"@/idl/poe\";\nimport { BN, Program } from \"@coral-xyz/anchor\";\nimport { Wa"
},
{
"path": "hooks/mutations/useRegisterUser.tsx",
"chars": 2770,
"preview": "import { Poe } from \"@/idl/poe\";\nimport { Program } from \"@coral-xyz/anchor\";\nimport { WalletContextState } from \"@solan"
},
{
"path": "hooks/mutations/useUpdateEstimate.ts",
"chars": 4675,
"preview": "import { Poe } from \"@/idl/poe\";\nimport { BN, Program } from \"@coral-xyz/anchor\";\nimport { WalletContextState } from \"@s"
},
{
"path": "hooks/queries/useAllPolls.ts",
"chars": 840,
"preview": "import { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport { Program } from \"@coral-xyz/anchor\";\nimport "
},
{
"path": "hooks/queries/useAllPollsByUser.ts",
"chars": 1263,
"preview": "import { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport { Program } from \"@coral-xyz/anchor\";\nimport "
},
{
"path": "hooks/queries/useAllUserAccounts.ts",
"chars": 713,
"preview": "import { useQuery } from \"@tanstack/react-query\";\nimport { Program } from \"@coral-xyz/anchor\";\nimport { Poe } from \"@/id"
},
{
"path": "hooks/queries/useAllUserPredictions.ts",
"chars": 952,
"preview": "import { useQuery } from \"@tanstack/react-query\";\nimport { PublicKey } from \"@solana/web3.js\";\nimport { Program } from \""
},
{
"path": "hooks/queries/useEstimateUpdatesByPoll.ts",
"chars": 4168,
"preview": "import { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport { BN, Program } from \"@coral-xyz/anchor\";\nimp"
},
{
"path": "hooks/queries/usePollById.ts",
"chars": 800,
"preview": "import { useQuery } from \"@tanstack/react-query\";\nimport { PublicKey } from \"@solana/web3.js\";\nimport { BN, Program } fr"
},
{
"path": "hooks/queries/useUserAccount.ts",
"chars": 1159,
"preview": "import { useQuery } from \"@tanstack/react-query\";\nimport { Connection, PublicKey } from \"@solana/web3.js\";\nimport { Prog"
},
{
"path": "hooks/queries/useUserBonkBalance.ts",
"chars": 1220,
"preview": "import { useQuery } from \"@tanstack/react-query\";\nimport { Connection, PublicKey } from \"@solana/web3.js\";\nimport { Prog"
},
{
"path": "hooks/queries/useUserEstimateByPoll.ts",
"chars": 1563,
"preview": "import { useQuery } from \"@tanstack/react-query\";\nimport { Connection, PublicKey } from \"@solana/web3.js\";\nimport { BN, "
},
{
"path": "hooks/queries/useUserScore.ts",
"chars": 1482,
"preview": "import { useQuery } from \"@tanstack/react-query\";\nimport { Connection, PublicKey } from \"@solana/web3.js\";\nimport { BN, "
},
{
"path": "hooks/queries/useUserSolBalance.ts",
"chars": 836,
"preview": "import { useQuery } from \"@tanstack/react-query\";\nimport { Connection, PublicKey, LAMPORTS_PER_SOL } from \"@solana/web3."
},
{
"path": "hooks/states/useTabStore.tsx",
"chars": 293,
"preview": "import { create } from \"zustand\";\n\ntype TabState = \"all\" | \"coming\";\n\ninterface TabsState {\n tab: TabState;\n setTab: ("
},
{
"path": "hooks/useAnchorProgram.tsx",
"chars": 1049,
"preview": "import { useEffect, useState } from \"react\";\nimport { AnchorProvider, Idl, Program } from \"@coral-xyz/anchor\";\nimport { "
},
{
"path": "hooks/useIntersectionObserver.tsx",
"chars": 781,
"preview": "import { MutableRefObject, useEffect, useState } from \"react\";\n\ninterface IntersectionObserverOptions {\n root?: Element"
},
{
"path": "idl/poe.json",
"chars": 43116,
"preview": "{\n \"address\": \"ACyH6Avm4uYen8WWyTU4chExQqpF4gCHy5MmtqtpWomk\",\n \"metadata\": {\n \"name\": \"poe\",\n \"version\": \"0.1.0\""
},
{
"path": "idl/poe.ts",
"chars": 36494,
"preview": "/**\n * Program IDL in camelCase format in order to be used in JS/TS.\n *\n * Note that this is only a type helper and is n"
},
{
"path": "lib/dummyData.ts",
"chars": 11052,
"preview": "export type Match = {\n id: string;\n date: string;\n teamA: string;\n teamB: string;\n logoA: string;\n logoB: string;\n"
},
{
"path": "lib/types.ts",
"chars": 662,
"preview": "import { PublicKey } from \"@solana/web3.js\";\n\nexport type Poll = {\n creator: PublicKey;\n resolver: PublicKey;\n open: "
},
{
"path": "lib/utils.ts",
"chars": 166,
"preview": "import { type ClassValue, clsx } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: Cla"
},
{
"path": "next.config.js",
"chars": 474,
"preview": "module.exports = {\n images: {\n remotePatterns: [\n {\n protocol: \"https\",\n hostname: \"via.placehold"
},
{
"path": "next.config.mjs",
"chars": 92,
"preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {};\n\nexport default nextConfig;\n"
},
{
"path": "package.json",
"chars": 1546,
"preview": "{\n \"name\": \"uefa-poe\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev\",\n \"build\": \"nex"
},
{
"path": "postcss.config.mjs",
"chars": 6006,
"preview": "import { createRequire } from 'module';\nconst require = createRequire(import.meta.url);\n/** @type {import('postcss-load-"
},
{
"path": "styles/globals.css",
"chars": 4523,
"preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n :root {\n --background: 0 0% 100%;\n --f"
},
{
"path": "tailwind.config.ts",
"chars": 2329,
"preview": "import type { Config } from \"tailwindcss\";\nconst { fontFamily } = require(\"tailwindcss/defaultTheme\");\n\nconst config = {"
},
{
"path": "texts/toastTitles.ts",
"chars": 136,
"preview": "export const transactionSuccessfullText = \"Transaction successfull 🥳\";\n\nexport const connectWalletText = \"Please connect"
},
{
"path": "tsconfig.json",
"chars": 574,
"preview": "{\n \"compilerOptions\": {\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n "
},
{
"path": "utils/sendVersionedTransaction.ts",
"chars": 1442,
"preview": "import { WalletNotConnectedError } from \"@/errors/WalletNotConnectedError\";\nimport { connectWalletText } from \"@/texts/t"
}
]
About this extraction
This page contains the full source code of the ProofOfEstimate/uefa-poe GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 75 files (240.2 KB), approximately 62.9k tokens, and a symbol index with 53 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.