Skip to content

Commit 87cf0e3

Browse files
modified frontend
1 parent 39b9210 commit 87cf0e3

25 files changed

Lines changed: 3061 additions & 146 deletions

frontend/app/campaign/id/page.tsx

Lines changed: 484 additions & 0 deletions
Large diffs are not rendered by default.

frontend/app/create/page.tsx

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
"use client"
2+
3+
import { useState, useEffect } from "react"
4+
import { useRouter } from "next/navigation"
5+
import { ethers } from "ethers"
6+
import { motion } from "framer-motion"
7+
import { Button } from "@/components/ui/button"
8+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
9+
import { Input } from "@/components/ui/input"
10+
import { Label } from "@/components/ui/label"
11+
import { Textarea } from "@/components/ui/textarea"
12+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
13+
import ConnectWallet from "@/components/connect-wallet"
14+
import { getContract } from "@/lib/contract"
15+
import { useToast } from "@/hooks/use-toast"
16+
import { ArrowLeft, Calendar, CircleDollarSign, FileText, Loader2, PenLine } from "lucide-react"
17+
import Link from "next/link"
18+
19+
export default function CreateCampaign() {
20+
const [name, setName] = useState("")
21+
const [description, setDescription] = useState("")
22+
const [target, setTarget] = useState("")
23+
const [deadline, setDeadline] = useState("")
24+
const [isLoading, setIsLoading] = useState(false)
25+
const [account, setAccount] = useState(null)
26+
const [provider, setProvider] = useState(null)
27+
const [activeTab, setActiveTab] = useState("details")
28+
29+
const router = useRouter()
30+
const { toast } = useToast()
31+
32+
useEffect(() => {
33+
const init = async () => {
34+
if (window.ethereum) {
35+
try {
36+
const provider = new ethers.BrowserProvider(window.ethereum)
37+
setProvider(provider)
38+
39+
const accounts = await provider.listAccounts()
40+
if (accounts.length > 0) {
41+
setAccount(accounts[0].address)
42+
}
43+
} catch (error) {
44+
console.error("Error initializing:", error)
45+
}
46+
}
47+
}
48+
49+
init()
50+
}, [])
51+
52+
const handleAccountChange = (newAccount) => {
53+
setAccount(newAccount)
54+
}
55+
56+
const handleSubmit = async (e) => {
57+
e.preventDefault()
58+
59+
if (!account) {
60+
toast({
61+
title: "Wallet not connected",
62+
description: "Please connect your wallet to create a campaign",
63+
variant: "destructive",
64+
})
65+
return
66+
}
67+
68+
if (!name || !description || !target || !deadline) {
69+
toast({
70+
title: "Missing fields",
71+
description: "Please fill in all fields",
72+
variant: "destructive",
73+
})
74+
return
75+
}
76+
77+
try {
78+
setIsLoading(true)
79+
const signer = await provider.getSigner()
80+
const contract = await getContract(signer)
81+
82+
const targetInWei = ethers.parseEther(target)
83+
const deadlineInDays = Number.parseInt(deadline)
84+
85+
const tx = await contract.createCampaign(name, description, targetInWei, deadlineInDays)
86+
87+
toast({
88+
title: "Transaction submitted",
89+
description: "Your campaign creation transaction has been submitted",
90+
})
91+
92+
await tx.wait()
93+
94+
toast({
95+
title: "Campaign created",
96+
description: "Your campaign has been created successfully",
97+
})
98+
99+
router.push("/")
100+
} catch (error) {
101+
console.error("Error creating campaign:", error)
102+
toast({
103+
title: "Error creating campaign",
104+
description: error.message || "An error occurred while creating the campaign",
105+
variant: "destructive",
106+
})
107+
} finally {
108+
setIsLoading(false)
109+
}
110+
}
111+
112+
const nextTab = () => {
113+
if (activeTab === "details") {
114+
if (!name || !description) {
115+
toast({
116+
title: "Missing information",
117+
description: "Please fill in all fields before proceeding",
118+
variant: "destructive",
119+
})
120+
return
121+
}
122+
setActiveTab("funding")
123+
}
124+
}
125+
126+
const prevTab = () => {
127+
if (activeTab === "funding") {
128+
setActiveTab("details")
129+
}
130+
}
131+
132+
return (
133+
<div className="container mx-auto px-4 py-12">
134+
<Link href="/" className="inline-flex items-center text-sm mb-8 hover:text-primary transition-colors">
135+
<ArrowLeft className="mr-2 h-4 w-4" />
136+
Back to campaigns
137+
</Link>
138+
139+
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}>
140+
<Card className="max-w-2xl mx-auto border shadow-lg">
141+
<CardHeader className="space-y-1">
142+
<CardTitle className="text-2xl">Create a Campaign</CardTitle>
143+
<CardDescription>Start a new crowdfunding campaign on FundChain</CardDescription>
144+
</CardHeader>
145+
146+
{!account ? (
147+
<CardContent className="text-center py-10">
148+
<motion.div
149+
initial={{ scale: 0.9, opacity: 0 }}
150+
animate={{ scale: 1, opacity: 1 }}
151+
transition={{ duration: 0.5 }}
152+
className="mb-6 mx-auto flex h-20 w-20 items-center justify-center rounded-full bg-primary/10"
153+
>
154+
<CircleDollarSign className="h-10 w-10 text-primary" />
155+
</motion.div>
156+
<h3 className="text-xl font-medium mb-2">Connect Your Wallet</h3>
157+
<p className="mb-6 text-muted-foreground">You need to connect your wallet to create a campaign</p>
158+
<ConnectWallet account={account} onAccountChange={handleAccountChange} buttonText="Connect Wallet" />
159+
</CardContent>
160+
) : (
161+
<>
162+
<CardContent>
163+
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
164+
<TabsList className="grid w-full grid-cols-2 mb-8">
165+
<TabsTrigger value="details" className="gap-2">
166+
<PenLine className="h-4 w-4" />
167+
Campaign Details
168+
</TabsTrigger>
169+
<TabsTrigger value="funding" className="gap-2">
170+
<CircleDollarSign className="h-4 w-4" />
171+
Funding Goals
172+
</TabsTrigger>
173+
</TabsList>
174+
175+
<TabsContent value="details" className="space-y-4 mt-0">
176+
<div className="space-y-2">
177+
<Label htmlFor="name">Campaign Name</Label>
178+
<Input
179+
id="name"
180+
placeholder="Enter a catchy name for your campaign"
181+
value={name}
182+
onChange={(e) => setName(e.target.value)}
183+
required
184+
className="transition-all duration-200 focus:ring-2 focus:ring-primary/20"
185+
/>
186+
</div>
187+
<div className="space-y-2">
188+
<Label htmlFor="description">Description</Label>
189+
<Textarea
190+
id="description"
191+
placeholder="Describe your campaign in detail. What are you raising funds for? Why should people support you?"
192+
value={description}
193+
onChange={(e) => setDescription(e.target.value)}
194+
rows={6}
195+
required
196+
className="resize-none transition-all duration-200 focus:ring-2 focus:ring-primary/20"
197+
/>
198+
</div>
199+
<div className="pt-4 text-right">
200+
<Button onClick={nextTab} className="gap-2">
201+
Next Step
202+
<ArrowLeft className="h-4 w-4 rotate-180" />
203+
</Button>
204+
</div>
205+
</TabsContent>
206+
207+
<TabsContent value="funding" className="space-y-4 mt-0">
208+
<div className="space-y-2">
209+
<Label htmlFor="target" className="flex items-center gap-2">
210+
<CircleDollarSign className="h-4 w-4 text-muted-foreground" />
211+
Funding Target (ETH)
212+
</Label>
213+
<Input
214+
id="target"
215+
type="number"
216+
step="0.01"
217+
placeholder="0.00"
218+
value={target}
219+
onChange={(e) => setTarget(e.target.value)}
220+
required
221+
className="transition-all duration-200 focus:ring-2 focus:ring-primary/20"
222+
/>
223+
<p className="text-xs text-muted-foreground">Set the amount of ETH you need to raise</p>
224+
</div>
225+
<div className="space-y-2">
226+
<Label htmlFor="deadline" className="flex items-center gap-2">
227+
<Calendar className="h-4 w-4 text-muted-foreground" />
228+
Campaign Duration (days)
229+
</Label>
230+
<Input
231+
id="deadline"
232+
type="number"
233+
min="1"
234+
placeholder="30"
235+
value={deadline}
236+
onChange={(e) => setDeadline(e.target.value)}
237+
required
238+
className="transition-all duration-200 focus:ring-2 focus:ring-primary/20"
239+
/>
240+
<p className="text-xs text-muted-foreground">How many days will your campaign run for?</p>
241+
</div>
242+
<div className="pt-4 flex justify-between">
243+
<Button variant="outline" onClick={prevTab} className="gap-2">
244+
<ArrowLeft className="h-4 w-4" />
245+
Previous Step
246+
</Button>
247+
<Button type="submit" onClick={handleSubmit} disabled={isLoading}>
248+
{isLoading ? (
249+
<>
250+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
251+
Creating...
252+
</>
253+
) : (
254+
"Create Campaign"
255+
)}
256+
</Button>
257+
</div>
258+
</TabsContent>
259+
</Tabs>
260+
</CardContent>
261+
<CardFooter className="flex flex-col space-y-4 border-t bg-muted/50 px-6 py-4">
262+
<div className="flex items-start space-x-2 text-sm text-muted-foreground">
263+
<FileText className="mt-0.5 h-4 w-4 shrink-0" />
264+
<span>
265+
Your campaign will be publicly visible on the blockchain. Make sure all information is accurate.
266+
</span>
267+
</div>
268+
</CardFooter>
269+
</>
270+
)}
271+
</Card>
272+
</motion.div>
273+
</div>
274+
)
275+
}

frontend/app/globals.css

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,63 @@
1-
@import "tailwindcss";
1+
@tailwind base;
2+
@tailwind components;
3+
@tailwind utilities;
24

3-
:root {
4-
--background: #ffffff;
5-
--foreground: #171717;
6-
}
5+
@layer base {
6+
:root {
7+
--background: 0 0% 100%;
8+
--foreground: 222.2 84% 4.9%;
9+
--card: 0 0% 100%;
10+
--card-foreground: 222.2 84% 4.9%;
11+
--popover: 0 0% 100%;
12+
--popover-foreground: 222.2 84% 4.9%;
13+
--primary: 221.2 83.2% 53.3%;
14+
--primary-foreground: 210 40% 98%;
15+
--secondary: 210 40% 96.1%;
16+
--secondary-foreground: 222.2 47.4% 11.2%;
17+
--muted: 210 40% 96.1%;
18+
--muted-foreground: 215.4 16.3% 46.9%;
19+
--accent: 210 40% 96.1%;
20+
--accent-foreground: 222.2 47.4% 11.2%;
21+
--destructive: 0 84.2% 60.2%;
22+
--destructive-foreground: 210 40% 98%;
23+
--border: 214.3 31.8% 91.4%;
24+
--input: 214.3 31.8% 91.4%;
25+
--ring: 221.2 83.2% 53.3%;
26+
--radius: 0.5rem;
27+
}
728

8-
@theme inline {
9-
--color-background: var(--background);
10-
--color-foreground: var(--foreground);
11-
--font-sans: var(--font-geist-sans);
12-
--font-mono: var(--font-geist-mono);
29+
.dark {
30+
--background: 222.2 84% 4.9%;
31+
--foreground: 210 40% 98%;
32+
--card: 222.2 84% 4.9%;
33+
--card-foreground: 210 40% 98%;
34+
--popover: 222.2 84% 4.9%;
35+
--popover-foreground: 210 40% 98%;
36+
--primary: 217.2 91.2% 59.8%;
37+
--primary-foreground: 222.2 47.4% 11.2%;
38+
--secondary: 217.2 32.6% 17.5%;
39+
--secondary-foreground: 210 40% 98%;
40+
--muted: 217.2 32.6% 17.5%;
41+
--muted-foreground: 215 20.2% 65.1%;
42+
--accent: 217.2 32.6% 17.5%;
43+
--accent-foreground: 210 40% 98%;
44+
--destructive: 0 62.8% 30.6%;
45+
--destructive-foreground: 210 40% 98%;
46+
--border: 217.2 32.6% 17.5%;
47+
--input: 217.2 32.6% 17.5%;
48+
--ring: 224.3 76.3% 48%;
49+
}
1350
}
1451

15-
@media (prefers-color-scheme: dark) {
16-
:root {
17-
--background: #0a0a0a;
18-
--foreground: #ededed;
52+
@layer base {
53+
* {
54+
@apply border-border;
55+
}
56+
body {
57+
@apply bg-background text-foreground;
1958
}
2059
}
2160

22-
body {
23-
background: var(--background);
24-
color: var(--foreground);
25-
font-family: Arial, Helvetica, sans-serif;
61+
.bg-grid-white\/10 {
62+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='32' height='32' fill='none' stroke='rgb(255 255 255 / 0.1)'%3e%3cpath d='M0 .5H31.5V32'/%3e%3c/svg%3e");
2663
}

0 commit comments

Comments
 (0)