1+ 'use client' ;
2+
3+ import { useEffect , useState } from 'react' ;
4+ import { useParams } from 'next/navigation' ;
5+ import { Card , CardContent , CardHeader , CardTitle } from '@/components/ui/card' ;
6+ import { Button } from '@/components/ui/button' ;
7+ import { getMockExpectations , getMockMilestoneContent } from '@/lib/mock-api' ;
8+ import { fetchFromIPFS } from '@/lib/ipfs' ;
9+ import { Expectation , MilestoneContent } from '@/types/expectation' ;
10+ import VotingInterface from '@/components/VotingInterface' ;
11+
12+ import CountdownTimer from '@/components/CountdownTimer' ;
13+ import Link from 'next/link' ;
14+ import MarkdownRenderer from '@components/MarkdownRenderer' ;
15+
16+ export default function ExpectationPage ( ) {
17+ const params = useParams ( ) ;
18+ const uri = decodeURIComponent ( params . uri as string ) ;
19+
20+ const [ expectation , setExpectation ] = useState < Expectation | null > ( null ) ;
21+ const [ milestoneContent , setMilestoneContent ] = useState < MilestoneContent | null > ( null ) ;
22+ const [ isLoading , setIsLoading ] = useState ( true ) ;
23+ const [ error , setError ] = useState < string | null > ( null ) ;
24+
25+ useEffect ( ( ) => {
26+ const fetchData = async ( ) => {
27+ try {
28+ setIsLoading ( true ) ;
29+ setError ( null ) ;
30+
31+ // Find expectation by milestoneURI
32+ const expectations = await getMockExpectations ( ) ;
33+ const foundExpectation = expectations . find ( exp => exp . milestoneURI === uri ) ;
34+
35+ if ( ! foundExpectation ) {
36+ setError ( 'Expectation not found' ) ;
37+ return ;
38+ }
39+
40+ setExpectation ( foundExpectation ) ;
41+
42+ // Try to fetch from IPFS first, then fallback to mock
43+ let content : MilestoneContent | null = null ;
44+
45+ try {
46+ content = await fetchFromIPFS ( uri ) ;
47+ } catch ( ipfsError ) {
48+ console . warn ( 'IPFS fetch failed, using mock data:' , ipfsError ) ;
49+ }
50+
51+ if ( ! content ) {
52+ content = await getMockMilestoneContent ( uri ) ;
53+ }
54+
55+ if ( ! content ) {
56+ setError ( 'Failed to load milestone content' ) ;
57+ return ;
58+ }
59+
60+ setMilestoneContent ( content ) ;
61+ } catch ( err ) {
62+ console . error ( 'Error fetching expectation data:' , err ) ;
63+ setError ( 'Failed to load expectation data' ) ;
64+ } finally {
65+ setIsLoading ( false ) ;
66+ }
67+ } ;
68+
69+ if ( uri ) {
70+ fetchData ( ) ;
71+ }
72+ } , [ uri ] ) ;
73+
74+ const handleVoteSubmitted = ( ) => {
75+ // Refresh expectation data after vote
76+ if ( expectation ) {
77+ getMockExpectations ( ) . then ( expectations => {
78+ const updatedExpectation = expectations . find ( exp => exp . id === expectation . id ) ;
79+ if ( updatedExpectation ) {
80+ setExpectation ( updatedExpectation ) ;
81+ }
82+ } ) ;
83+ }
84+ } ;
85+
86+ if ( isLoading ) {
87+ return (
88+ < main className = "w-full h-full max-w-screen-2xl mx-auto" >
89+ < div className = "base:max-md:px-3 py-6 px-10" >
90+ < div className = "text-center py-20" >
91+ < div className = "text-ourWhite text-xl" > Loading expectation...</ div >
92+ </ div >
93+ </ div >
94+ </ main >
95+ ) ;
96+ }
97+
98+ if ( error || ! expectation || ! milestoneContent ) {
99+ return (
100+ < main className = "w-full h-full max-w-screen-2xl mx-auto" >
101+ < div className = "base:max-md:px-3 py-6 px-10" >
102+ < div className = "text-center py-20" >
103+ < div className = "text-red-400 text-xl mb-4" >
104+ { error || 'Expectation not found' }
105+ </ div >
106+ < Link href = "/dewhitepaper/expectations" >
107+ < Button variant = "outline" className = "text-ourWhite px-8 rounded-none bg-[transparent] border-gray-600 font-extralight font-lighten tracking-widest hover:border-gray-500 transition-all duration-200 ease-in-out hover:scale-[1.02]" >
108+ ← Back to Expectations
109+ </ Button >
110+ </ Link >
111+ </ div >
112+ </ div >
113+ </ main >
114+ ) ;
115+ }
116+
117+ const isVotingActive = expectation . status === 'ongoing' && expectation . deadline > Date . now ( ) ;
118+
119+ return (
120+ < main className = "w-full h-full max-w-screen-2xl mx-auto" >
121+ { /* Header */ }
122+ < header className = "py-2 h-fit base:max-md:px-3 px-10 items-center border-b border-gray-600 max-md:py-4 flex relative top-0 w-full" >
123+ < div className = "w-fit" >
124+ < p className = "bg-gradient-to-t mt-4 from-gray-400 mb-3 tab:text-8xl tracking-tighter text-4xl to-white bg-clip-text text-transparent font-bolden" >
125+ EXPECTATION< span className = "from-gray-400 tracking-tighter text-[10px] tab:text-[20px]" > noun</ span >
126+ </ p >
127+ < div className = "p-4" >
128+ < div className = "mx-auto px-6 relative h-fit min-h-32 border-l-gray-600 border-l-2 border-dashed" >
129+ < div id = "timeline-item" className = "text-ourWhite flex mb-5" >
130+ < span className = "absolute flex items-center justify-center -left-[23px] bg-cover bg-block rounded w-10 h-10" >
131+ 1
132+ </ span >
133+ < div className = "relative top-[8px]" >
134+ < dd className = "poppins-regular" >
135+ : a < strong className = "poppins-extrabold" > detailed proposal</ strong > for community review and voting
136+ </ dd >
137+ </ div >
138+ </ div >
139+ < div id = "timeline-item" className = "text-ourWhite flex" >
140+ < span className = "absolute flex items-center justify-center -left-[23px] bg-block bg-cover rounded w-10 h-10" >
141+ 2
142+ </ span >
143+ < div className = "relative top-[8px]" >
144+ < dd className = "poppins-regular" >
145+ : a < strong className = "poppins-extrabold" > milestone commitment</ strong > tied to treasury withdrawal requests
146+ </ dd >
147+ </ div >
148+ </ div >
149+ </ div >
150+ </ div >
151+ </ div >
152+ </ header >
153+
154+ { /* Content */ }
155+ < section className = "base:max-md:px-3 py-6 px-10" >
156+ < div className = "mb-8" >
157+ < div className = "flex items-start justify-between mb-4" >
158+ < div >
159+ < h1 className = "tab:text-4xl text-2xl mb-2 tracking-wide font-bolden text-ourWhite" >
160+ { milestoneContent . title }
161+ </ h1 >
162+ < div className = "flex items-center gap-4 text-sm text-gray-400" >
163+ < span className = { `px-2 py-1 rounded ${
164+ expectation . status === 'ongoing' ? 'bg-yellow-900/20 text-yellow-400' :
165+ expectation . status === 'fulfilled' ? 'bg-green-900/20 text-green-400' :
166+ 'bg-red-900/20 text-red-400'
167+ } `} >
168+ { expectation . status . toUpperCase ( ) }
169+ </ span >
170+ { isVotingActive && (
171+ < CountdownTimer deadline = { expectation . deadline } />
172+ ) }
173+ </ div >
174+ </ div >
175+ < Link href = "/dewhitepaper/expectations" >
176+ < Button variant = "outline" size = "sm" className = "text-ourWhite px-6 rounded-none bg-[transparent] border-gray-600 font-extralight font-lighten tracking-widest hover:border-gray-500 transition-all duration-200 ease-in-out hover:scale-[1.02]" >
177+ ← Back to Expectations
178+ </ Button >
179+ </ Link >
180+ </ div >
181+ </ div >
182+
183+ < div className = "grid grid-cols-1 lg:grid-cols-3 gap-8" >
184+ { /* Main Content */ }
185+ < div className = "lg:col-span-2 space-y-6" >
186+ { /* Proposal Content */ }
187+ < Card className = "bg-transparent text-white border-gray-600" >
188+ < CardHeader >
189+ < CardTitle className = "text-xl font-bolden text-ourWhite" >
190+ Proposal Details
191+ </ CardTitle >
192+ </ CardHeader >
193+ < CardContent >
194+ < MarkdownRenderer content = { milestoneContent . content } />
195+ </ CardContent >
196+ </ Card >
197+
198+ { /* GitHub Link */ }
199+ < Card className = "bg-transparent text-white border-gray-600" >
200+ < CardHeader >
201+ < CardTitle className = "text-lg font-bolden text-ourWhite" >
202+ Full Proposal
203+ </ CardTitle >
204+ </ CardHeader >
205+ < CardContent >
206+ < p className = "text-gray-300 poppins-regular mb-4" >
207+ View the complete proposal on GitHub for more detailed information and technical specifications.
208+ </ p >
209+ < a
210+ href = { milestoneContent . bipLink }
211+ target = "_blank"
212+ rel = "noopener noreferrer"
213+ className = "inline-flex items-center gap-2 text-blue-400 hover:text-blue-300 underline"
214+ >
215+ < svg className = "w-5 h-5" fill = "currentColor" viewBox = "0 0 20 20" >
216+ < path fillRule = "evenodd" d = "M10 0C4.477 0 0 4.484 0 10.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0110 4.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.203 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.942.359.31.678.921.678 1.856 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0020 10.017C20 4.484 15.522 0 10 0z" clipRule = "evenodd" />
217+ </ svg >
218+ View on GitHub
219+ </ a >
220+ </ CardContent >
221+ </ Card >
222+ </ div >
223+
224+ { /* Voting Sidebar */ }
225+ < div className = "lg:col-span-1" >
226+ < VotingInterface
227+ expectation = { expectation }
228+ onVoteSubmitted = { handleVoteSubmitted }
229+ />
230+ </ div >
231+ </ div >
232+ </ section >
233+ </ main >
234+ ) ;
235+ }
0 commit comments