Skip to content
This repository was archived by the owner on Mar 16, 2026. It is now read-only.

Commit 0761b51

Browse files
authored
Merge pull request #724 from aibtcdev/aibtc-la
Aibtc la
2 parents 40ae40f + ef2cd7f commit 0761b51

15 files changed

Lines changed: 407 additions & 94 deletions

File tree

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@
2020
],
2121
"[typescript]": {
2222
"editor.defaultFormatter": "vscode.typescript-language-features"
23-
}
23+
},
24+
"python-envs.defaultEnvManager": "ms-python.python:conda",
25+
"python-envs.defaultPackageManager": "ms-python.python:conda",
26+
"python-envs.pythonProjects": []
2427
}

src/components/aidaos/RootDAOPage.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { getStacksAddress } from "@/lib/address";
4444
// import { useAgentAccount } from "@/hooks/useAgentAccount";
4545
// import { BalanceDisplay } from "@/components/reusables/BalanceDisplay";
4646
import { TwitterCard } from "@/components/twitter/TwitterCard";
47+
import { rewardPerPassedProposal } from "@/config/features";
4748

4849
// Network configuration
4950
const isMainnet = process.env.NEXT_PUBLIC_STACKS_NETWORK === "mainnet";
@@ -373,6 +374,10 @@ export function RootDAOPage({ children, daoName }: RootDAOPageProps) {
373374
return proposals.filter((proposal) => proposal.passed === true).length;
374375
}, [proposals]);
375376

377+
const totalRewards = useMemo(() => {
378+
return passedProposals * rewardPerPassedProposal;
379+
}, [passedProposals]);
380+
376381
if (isBasicLoading || !dao) {
377382
return (
378383
<main className="flex h-screen w-full items-center justify-center">
@@ -482,7 +487,9 @@ export function RootDAOPage({ children, daoName }: RootDAOPageProps) {
482487
</div>
483488
<div className="flex items-center gap-2">
484489
<span className="font-bold">Total Rewards:</span>
485-
<span className="font-medium">$4,005</span>
490+
<span className="font-medium">
491+
${totalRewards.toLocaleString()}
492+
</span>
486493
</div>
487494
</div>
488495
</div>
@@ -520,19 +527,23 @@ export function RootDAOPage({ children, daoName }: RootDAOPageProps) {
520527
<TwitterCard
521528
name="AIBTC"
522529
username="aibtcdev"
523-
tweet={`AIBTC order update — proof of coffee ritual ☕
530+
tweet={`AIBTC order — LA interviews 🌴
531+
532+
hit the streets of LA and record a video asking a stranger:
533+
534+
• "What do you love about LA?"
535+
• "What's one thing you'd fix about LA?"
536+
537+
quote this post with the video and neighborhood you were in
524538
525-
quote this post with:
526-
1️⃣ a coffee selfie
527-
2️⃣ tag a blue-check builder you respect
528-
3️⃣ tell us what you've built today for bitcoin
539+
approved entries earn $50 BTC
529540
530-
submit daily at aibtc.com`}
531-
date="Nov 7, 2025"
532-
time="8:14 PM"
541+
details in next post`}
542+
date="Nov 25, 2025"
543+
// time="8:14 PM"
533544
avatarUrl="/logos/aibtcdev-avatar-250px.png"
534545
verified={true}
535-
link="https://x.com/aibtcdev/status/1985732853540470816"
546+
// link="https://x.com/aibtcdev/status/1990455534554841152"
536547
title="The Current Order"
537548
/>
538549
</div>

src/components/proposals/ProposalCard.tsx

Lines changed: 126 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { useProposalVote } from "@/hooks/useProposalVote";
1616
import { Button } from "@/components/ui/button";
1717
import { RefreshCw, AlertCircle } from "lucide-react";
1818
import { motion } from "framer-motion";
19+
import { safeNumberFromBigInt } from "@/utils/proposal";
20+
import { cn } from "@/lib/utils";
1921

2022
interface ProposalCardProps {
2123
proposal: Proposal | ProposalWithDAO;
@@ -31,14 +33,16 @@ export default function ProposalCard({
3133
const router = useRouter();
3234

3335
// Use the unified status system
34-
const { statusConfig, isActive, isPassed } = useProposalStatus(proposal);
36+
const { status, statusConfig, isActive } = useProposalStatus(proposal);
3537

3638
// Use centralized vote hook for consistent data fetching
3739
const {
3840
voteDisplayData,
41+
calculations,
3942
error: hasVoteDataError,
4043
refreshVoteData,
4144
isLoading: isLoadingVotes,
45+
vetoCheck,
4246
} = useProposalVote({
4347
proposal,
4448
contractPrincipal: proposal.contract_principal,
@@ -90,6 +94,43 @@ export default function ProposalCard({
9094
// const liquidTokens = Number(proposal.liquid_tokens);
9195
const { totalVotes, hasVoteData } = voteSummary;
9296

97+
// Enhanced calculations for quorum and threshold display
98+
const enhancedCalculations = useMemo(() => {
99+
if (!calculations) return null;
100+
101+
const quorumPercentage = safeNumberFromBigInt(proposal.voting_quorum);
102+
const thresholdPercentage = safeNumberFromBigInt(proposal.voting_threshold);
103+
104+
// Calculate if requirements are met
105+
const metQuorum = calculations.participationRate >= quorumPercentage;
106+
const metThreshold =
107+
calculations.totalVotes > 0
108+
? calculations.approvalRate >= thresholdPercentage
109+
: false;
110+
111+
return {
112+
...calculations,
113+
quorumPercentage,
114+
thresholdPercentage,
115+
metQuorum,
116+
metThreshold,
117+
};
118+
}, [calculations, proposal]);
119+
120+
// Helper function for status display
121+
const getStatusText = (met: boolean, percentage?: number) => {
122+
// If voting hasn't started (PENDING, DRAFT), show "Pending"
123+
if (status === "PENDING" || status === "DRAFT") {
124+
return "Pending";
125+
}
126+
127+
if (isActive) {
128+
return percentage !== undefined ? `${percentage.toFixed(1)}%` : "0%";
129+
}
130+
131+
return met ? "Passed" : "Failed";
132+
};
133+
93134
// Memoize DAO info
94135
const daoInfo = useMemo(() => {
95136
const proposalWithDAO = proposal as ProposalWithDAO;
@@ -130,12 +171,93 @@ export default function ProposalCard({
130171
? `#${proposal.proposal_id}: ${proposal.title}`
131172
: proposal.title}
132173
</h3>
133-
<div className="flex items-center gap-2 sm:gap-3 mb-2">
174+
<div className="flex flex-wrap items-center gap-2 mb-2">
134175
<ProposalStatusBadge
135176
proposal={proposal}
136177
size="sm"
137178
className="flex-shrink-0"
138179
/>
180+
181+
{/* Quorum and Threshold Badges */}
182+
{enhancedCalculations &&
183+
statusConfig.label !== "Pending" &&
184+
statusConfig.label !== "Draft" && (
185+
<>
186+
{/* Quorum Badge */}
187+
<div
188+
className={cn(
189+
"px-2 py-0.5 rounded-sm text-xs font-medium border flex-shrink-0",
190+
isActive
191+
? enhancedCalculations.metQuorum
192+
? "bg-success/10 border-success/20"
193+
: "bg-primary/10 border-primary/20"
194+
: enhancedCalculations.metQuorum
195+
? "bg-success/10 border-success/20"
196+
: "bg-destructive/10 border-destructive/20"
197+
)}
198+
>
199+
<span className="text-muted-foreground">Quorum:</span>{" "}
200+
<span
201+
className={cn(
202+
isActive
203+
? enhancedCalculations.metQuorum
204+
? "text-success"
205+
: "text-primary"
206+
: enhancedCalculations.metQuorum
207+
? "text-success"
208+
: "text-destructive"
209+
)}
210+
>
211+
{getStatusText(
212+
enhancedCalculations.metQuorum,
213+
enhancedCalculations.participationRate
214+
)}
215+
</span>
216+
</div>
217+
218+
{/* Threshold Badge */}
219+
<div
220+
className={cn(
221+
"px-2 py-0.5 rounded-sm text-xs font-medium border flex-shrink-0",
222+
isActive
223+
? enhancedCalculations.metThreshold
224+
? "bg-success/10 border-success/20"
225+
: "bg-primary/10 border-primary/20"
226+
: enhancedCalculations.metThreshold
227+
? "bg-success/10 border-success/20"
228+
: "bg-destructive/10 border-destructive/20"
229+
)}
230+
>
231+
<span className="text-muted-foreground">
232+
Threshold:
233+
</span>{" "}
234+
<span
235+
className={cn(
236+
isActive
237+
? enhancedCalculations.metThreshold
238+
? "text-success"
239+
: "text-primary"
240+
: enhancedCalculations.metThreshold
241+
? "text-success"
242+
: "text-destructive"
243+
)}
244+
>
245+
{getStatusText(
246+
enhancedCalculations.metThreshold,
247+
enhancedCalculations.approvalRate
248+
)}
249+
</span>
250+
</div>
251+
</>
252+
)}
253+
254+
{/* Veto Override Warning Badge */}
255+
{vetoCheck?.vetoExceedsForVote && !isActive && (
256+
<div className="px-2 py-0.5 rounded-sm text-xs font-medium border flex-shrink-0 bg-destructive/10 border-destructive/20">
257+
<span className="text-destructive">⚠️ Vetoed</span>
258+
</div>
259+
)}
260+
139261
<div className="flex items-center gap-1 text-xs text-foreground/75 flex-shrink-0">
140262
<Clock className="h-3 w-3 flex-shrink-0" />
141263
<span className="whitespace-nowrap">
@@ -347,28 +469,11 @@ export default function ProposalCard({
347469
</div>
348470
)}
349471

350-
{/* Completed Status */}
351-
{/* {isPassed && (
352-
<div className="text-sm">
353-
<span className="text-foreground/75">Final result: </span>
354-
<span className="font-medium">
355-
<span className="text-success">
356-
<TokenBalance variant="abbreviated" value={votesFor} /> For
357-
</span>
358-
,{" "}
359-
<span className="text-destructive">
360-
<TokenBalance variant="abbreviated" value={votesAgainst} />{" "}
361-
Against
362-
</span>
363-
</span>
364-
</div>
365-
)} */}
366-
367-
{/* Enhanced Chart Section for detailed view - Hide for pending proposals */}
472+
{/* Vote Status Chart - Show for active, veto period, execution window, passed, and failed proposals */}
368473
{(isActive ||
369474
statusConfig.label === "Veto Period" ||
370475
statusConfig.label === "Execution Window" ||
371-
isPassed ||
476+
statusConfig.label === "Passed" ||
372477
statusConfig.label === "Failed") &&
373478
statusConfig.label !== "Pending" && (
374479
<div className="">

src/components/proposals/ProposalSubmission.tsx

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ export function ProposalSubmission({
264264
const [xUsernameError, setXUsernameError] = useState<string | null>(null);
265265
const [xProfile, setXProfile] = useState<XProfile | null>(null);
266266

267+
// Consent checkbox state
268+
const [consentChecked, setConsentChecked] = useState(false);
269+
267270
const { accessToken, isLoading: isSessionLoading, userId } = useAuth();
268271
const {
269272
needsXLink,
@@ -811,6 +814,7 @@ export function ProposalSubmission({
811814
if (isSuccess) {
812815
setTxStatusView("confirmed-success");
813816
setTwitterUrl("");
817+
setConsentChecked(false);
814818
// setSelectedAirdropTxHash(null); // Commented out - airdrop feature disabled
815819
} else if (isFailed) setTxStatusView("confirmed-failure");
816820

@@ -1319,6 +1323,28 @@ export function ProposalSubmission({
13191323
</div>
13201324
</div>
13211325
)}
1326+
1327+
{/* Blocked User Lock Overlay */}
1328+
{hasAccessToken &&
1329+
!needsXLink &&
1330+
!isXLoading &&
1331+
profile?.is_blocked === true && (
1332+
<div className="absolute inset-0 bg-zinc-900 rounded-sm flex flex-col items-center justify-center z-10">
1333+
<div className="text-center space-y-4 max-w-md mx-auto px-6">
1334+
<div className="w-16 h-16 rounded-sm bg-red-900/20 border border-red-800/30 flex items-center justify-center mx-auto">
1335+
<Lock className="w-8 h-8 text-red-400" />
1336+
</div>
1337+
<div>
1338+
<h3 className="text-xl font-bold text-red-300 mb-2">
1339+
Account Restricted
1340+
</h3>
1341+
<p className="text-sm text-red-200/80 leading-relaxed">
1342+
Your account is restricted from submitting contributions.
1343+
</p>
1344+
</div>
1345+
</div>
1346+
</div>
1347+
)}
13221348
{/* Airdrop notification - Commented out per user request */}
13231349
{/* {hasAccessToken && (
13241350
<div className="bg-secondary/40 rounded-sm p-3 shadow-sm">
@@ -1668,6 +1694,28 @@ export function ProposalSubmission({
16681694
</form>
16691695
</div>
16701696

1697+
{/* Consent Checkbox - Outside overlays */}
1698+
{hasAccessToken && (
1699+
<div className="flex items-start gap-3 rounded-sm">
1700+
<input
1701+
type="checkbox"
1702+
id="consent-checkbox"
1703+
checked={consentChecked}
1704+
onChange={(e) => setConsentChecked(e.target.checked)}
1705+
disabled={isSubmitting || isLoadingExtensions || isLoadingAgents}
1706+
className="mt-1 h-4 w-4 rounded border-white/10 bg-background text-primary cursor-pointer"
1707+
/>
1708+
<label
1709+
htmlFor="consent-checkbox"
1710+
className="text-sm text-muted-foreground leading-relaxed cursor-pointer select-none"
1711+
>
1712+
By submitting, you confirm you obtained consent from everyone
1713+
identifiable in these photos or videos and authorize AIBTC to
1714+
share and repost them.
1715+
</label>
1716+
</div>
1717+
)}
1718+
16711719
{/* Footer CTA */}
16721720
<div className="pt-6">
16731721
{!hasAccessToken ? (
@@ -1679,6 +1727,7 @@ export function ProposalSubmission({
16791727
disabled={
16801728
!twitterUrl.trim() ||
16811729
!isValidTwitterUrl ||
1730+
!consentChecked ||
16821731
isSubmitting ||
16831732
isValidatingXUsername ||
16841733
!hasAgentAccount ||
@@ -1694,7 +1743,8 @@ export function ProposalSubmission({
16941743
isLoadingEmbed ||
16951744
!twitterEmbedData ||
16961745
!!xUsernameError ||
1697-
!canSubmitContribution
1746+
!canSubmitContribution ||
1747+
profile?.is_blocked === true
16981748
}
16991749
className="bg-primary hover:bg-primary/90 text-primary-foreground font-bold px-6 py-2 text-sm rounded-sm shadow-md hover:shadow-lg transition-all duration-200"
17001750
>
@@ -1746,6 +1796,11 @@ export function ProposalSubmission({
17461796
<Loader />
17471797
<span className="break-words">Loading Post Content...</span>
17481798
</div>
1799+
) : profile?.is_blocked === true ? (
1800+
<div className="flex items-center gap-2">
1801+
<Lock className="w-4 h-4" />
1802+
<span>Account Restricted</span>
1803+
</div>
17491804
) : (
17501805
<div className="flex items-center gap-2">
17511806
<Send className="h-4 w-4" />

0 commit comments

Comments
 (0)