Skip to content

Commit 2686664

Browse files
committed
chore: implement todo list
1 parent f5674f4 commit 2686664

5 files changed

Lines changed: 247 additions & 154 deletions

File tree

frontend/app/layout.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
11
"use client";
22
import "./globals.css";
3+
import {
4+
StarknetConfig,
5+
useInjectedConnectors,
6+
argent,
7+
braavos,
8+
publicProvider,
9+
voyager
10+
} from "@starknet-react/core";
11+
import { mainnet, sepolia } from "@starknet-react/chains";
312

413
export default function RootLayout({
514
children,
615
}: Readonly<{
716
children: React.ReactNode;
817
}>) {
18+
const { connectors } = useInjectedConnectors({
19+
recommended: [argent(), braavos()],
20+
21+
includeRecommended: "onlyIfNoConnectors",
22+
order: "alphabetical",
23+
});
24+
925
return (
1026
<html>
11-
<body>{children}</body>
27+
<body>
28+
<StarknetConfig
29+
chains={[mainnet, sepolia]}
30+
connectors={connectors}
31+
provider={publicProvider()}
32+
explorer={voyager}
33+
autoConnect
34+
>
35+
{children}
36+
</StarknetConfig>
37+
</body>
1238
</html>
1339
);
1440
}

frontend/app/page.tsx

Lines changed: 96 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
"use client";
2-
import { useState } from "react";
2+
import { useEffect, useState } from "react";
33
import { Circle, X } from "lucide-react";
44
import Navbar from "../components/Navbar";
5+
import {
6+
useConnect,
7+
useContract,
8+
useReadContract,
9+
useSendTransaction,
10+
} from "@starknet-react/core";
11+
import { TODO_ABI } from "@/constants/abi";
12+
import { TODO_CONTRACT_ADDRESS } from "@/constants";
13+
import { shortString } from "starknet";
514

615
type Todo = {
716
id: string;
@@ -10,25 +19,63 @@ type Todo = {
1019
};
1120

1221
export default function TodoList() {
13-
const [todos, setTodos] = useState<Todo[]>([
14-
{ id: "1", text: "Join Starknet Discord", status: "pending" },
15-
{ id: "2", text: "Write your first Cairo contract", status: "pending" },
16-
{ id: "3", text: "Deploy a dApp on Starknet", status: "completed" },
17-
]);
22+
const [todos, setTodos] = useState<Todo[]>();
1823
const [input, setInput] = useState("");
1924

25+
const { sendAsync } = useSendTransaction({ calls: [] });
26+
const { contract } = useContract({
27+
abi: TODO_ABI,
28+
address: TODO_CONTRACT_ADDRESS,
29+
});
30+
31+
const { data, isFetching, error } = useReadContract({
32+
abi: TODO_ABI,
33+
address: TODO_CONTRACT_ADDRESS,
34+
functionName: "get_todo_list",
35+
args: [],
36+
});
37+
2038
const addTodo = async () => {
21-
// add todo
39+
if (!input) return;
40+
if (input.length > 31) alert("Text can not be more than 31 characters");
41+
42+
const calls = contract?.populate("add_todo", [input]);
43+
if (calls) {
44+
await sendAsync([calls]);
45+
alert("Todo added succesfully");
46+
}
2247
};
2348

24-
const deleteTodo = async (index: number) => {
49+
const deleteTodo = async (id: number) => {
50+
const calls = contract?.populate("delete_todo", [id]);
51+
if (calls) {
52+
await sendAsync([calls]);
53+
alert("Todo deleted successfully");
54+
}
55+
2556
// delete todo
2657
};
2758

28-
const toggleStatus = async (index: number) => {
29-
// toggle status
59+
const toggleStatus = async (id: number) => {
60+
const calls = contract?.populate("complete_todo", [id]);
61+
if (calls) {
62+
await sendAsync([calls]);
63+
alert("Todo status changed successfully");
64+
}
3065
};
3166

67+
useEffect(() => {
68+
if (data && Array.isArray(data)) {
69+
const result = data.map((todo) => ({
70+
id: shortString.decodeShortString(todo.id.toString()),
71+
text: shortString.decodeShortString(todo.todo_description.toString()),
72+
status: shortString.decodeShortString(todo.status.toString()),
73+
}));
74+
75+
setTodos(result);
76+
}
77+
}, [data]);
78+
3279
return (
3380
<div className="min-h-screen bg-[#0d1117] text-white ">
3481
<Navbar />
@@ -57,46 +104,50 @@ export default function TodoList() {
57104
</button>
58105
</div>
59106

60-
<ul className="space-y-5">
61-
{todos &&
62-
todos.map(({ status, text }, index) => (
63-
<li
64-
key={index}
65-
className="flex items-center justify-between px-6 py-4 rounded-2xl bg-[#161b22] hover:shadow-lg transition-all duration-300 border border-transparent hover:border-[#5C94FF]"
66-
>
67-
<div
68-
className={`flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 cursor-pointer ${
69-
status == "completed"
70-
? "text-gray-500 line-through"
71-
: "text-white font-medium"
72-
}`}
73-
onClick={() => toggleStatus(index)}
107+
{isFetching ? (
108+
<p className="text-center">Loading...</p>
109+
) : (
110+
<ul className="space-y-5">
111+
{todos &&
112+
todos.map(({ status, text, id }, index) => (
113+
<li
114+
key={index}
115+
className="flex items-center justify-between px-6 py-4 rounded-2xl bg-[#161b22] hover:shadow-lg transition-all duration-300 border border-transparent hover:border-[#5C94FF]"
74116
>
75-
<div className="flex items-center gap-3">
76-
<Circle className="text-[#5C94FF] w-5 h-5 shrink-0" />
77-
<span className="text-sm md:text-base">{text}</span>
78-
</div>
79-
80-
<span
81-
className={`text-xs py-1 px-3 rounded-full font-semibold ${
117+
<div
118+
className={`flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 cursor-pointer ${
82119
status == "completed"
83-
? "bg-green-700 text-green-300"
84-
: "bg-yellow-700 text-yellow-300"
120+
? "text-gray-500 line-through"
121+
: "text-white font-medium"
85122
}`}
123+
onClick={() => toggleStatus(Number(id))}
86124
>
87-
{status}
88-
</span>
89-
</div>
125+
<div className="flex items-center gap-3">
126+
<Circle className="text-[#5C94FF] w-5 h-5 shrink-0" />
127+
<span className="text-sm md:text-base">{text}</span>
128+
</div>
90129

91-
<button
92-
onClick={() => deleteTodo(index)}
93-
className="text-gray-400 hover:text-red-500 transition"
94-
>
95-
<X className="w-5 h-5" />
96-
</button>
97-
</li>
98-
))}
99-
</ul>
130+
<span
131+
className={`text-xs py-1 px-3 rounded-full font-semibold ${
132+
status == "completed"
133+
? "bg-green-700 text-green-300"
134+
: "bg-yellow-700 text-yellow-300"
135+
}`}
136+
>
137+
{status}
138+
</span>
139+
</div>
140+
141+
<button
142+
onClick={() => deleteTodo(index)}
143+
className="text-gray-400 hover:text-red-500 transition"
144+
>
145+
<X className="w-5 h-5" />
146+
</button>
147+
</li>
148+
))}
149+
</ul>
150+
)}
100151
</div>
101152
</div>
102153
</div>

frontend/components/Navbar.tsx

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,22 @@
22

33
import * as Dialog from "@radix-ui/react-dialog";
44
import { X, UserCircle2 } from "lucide-react";
5-
import { useState } from "react";
5+
import { useState } from "react";
6+
import { useConnect, useAccount, Connector, useDisconnect } from "@starknet-react/core";
67

78
export default function Navbar() {
89
const [isOpen, setIsOpen] = useState(false);
910

11+
const { connectors, connect, connectAsync } = useConnect();
12+
13+
const { account, address, isConnected } = useAccount();
14+
15+
16+
const handleConnect = async (connector: Connector) => {
17+
await connectAsync({ connector });
18+
setIsOpen(false);
19+
};
20+
1021
return (
1122
<nav className="w-full border border-[#1c1f26] px-6 py-4">
1223
<div className="flex items-center justify-between">
@@ -16,14 +27,16 @@ export default function Navbar() {
1627
</span>
1728
</h1>
1829

19-
{/* <ProfileBar address={account.address} /> */}
20-
21-
<button
22-
onClick={() => setIsOpen(true)}
23-
className="px-4 md:px-4 py-3 text-base md:text-lg font-bold bg-gradient-to-r from-[#FC8181] to-[#5C94FF] rounded-full hover:opacity-90 transition-all duration-200"
24-
>
25-
Connect Wallet
26-
</button>
30+
{isConnected && account ? (
31+
<ProfileBar address={account.address} />
32+
) : (
33+
<button
34+
onClick={() => setIsOpen(true)}
35+
className="px-4 md:px-4 py-3 text-base md:text-lg font-bold bg-gradient-to-r from-[#FC8181] to-[#5C94FF] rounded-full hover:opacity-90 transition-all duration-200"
36+
>
37+
Connect Wallet
38+
</button>
39+
)}
2740
</div>
2841

2942
<Dialog.Root open={isOpen} onOpenChange={() => setIsOpen(!isOpen)}>
@@ -46,13 +59,14 @@ export default function Navbar() {
4659
</p>
4760

4861
<div className="space-y-3">
49-
<button className="w-full py-2 rounded-md bg-[#5C94FF] text-white font-semibold hover:bg-[#487dd8] transition">
50-
Argent X
51-
</button>
52-
53-
<button className="w-full py-2 rounded-md bg-[#5C94FF] text-white font-semibold hover:bg-[#487dd8] transition">
54-
Braavos
55-
</button>
62+
{connectors.map((connector) => (
63+
<button
64+
onClick={() => handleConnect(connector)}
65+
className="w-full py-2 rounded-md bg-[#5C94FF] text-white font-semibold hover:bg-[#487dd8] transition"
66+
>
67+
{connector.name}
68+
</button>
69+
))}
5670
</div>
5771
</Dialog.Content>
5872
</Dialog.Portal>
@@ -62,12 +76,13 @@ export default function Navbar() {
6276
}
6377

6478
function ProfileBar({ address }: { address: string }) {
79+
const {disconnect} = useDisconnect()
6580

6681
return (
6782
<div className="flex items-center space-x-2 px-4 py-2 rounded-full border border-[#2d2f36] bg-[#1c1f26]">
6883
<UserCircle2 className="w-6 h-6 text-white" />
69-
70-
<button >disconnect</button>
84+
85+
<button onClick={() => disconnect()}>disconnect</button>
7186
</div>
7287
);
7388
}

0 commit comments

Comments
 (0)