Skip to content

Commit b564d21

Browse files
authored
Merge pull request #1248 from dfinity/fix/migrate-to-ts
migrate project to plain js
2 parents 85c1154 + d0d590a commit b564d21

File tree

19 files changed

+287
-327
lines changed

19 files changed

+287
-327
lines changed
Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
<html lang='en'>
2-
<head>
3-
<meta charset='UTF-8' />
4-
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
5-
<title>NFT Collection Management Application</title>
6-
</head>
7-
<body>
8-
<div id='root'></div>
9-
<script type='module' src='/src/main.tsx'></script>
10-
</body>
2+
3+
<head>
4+
<meta charset='UTF-8' />
5+
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
6+
<title>NFT Collection Management Application</title>
7+
</head>
8+
9+
<body>
10+
<div id='root'></div>
11+
<script type='module' src='/src/main.jsx'></script>
12+
</body>
13+
1114
</html>

motoko/nft-creator/frontend/package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"scripts": {
77
"dev": "vite",
88
"prebuild": "npm i --include=dev && dfx generate",
9-
"build": "tsc && vite build"
9+
"build": "vite build"
1010
},
1111
"dependencies": {
1212
"@dfinity/agent": "^3.0.0",
@@ -21,12 +21,9 @@
2121
"tailwindcss": "^4.1.11"
2222
},
2323
"devDependencies": {
24-
"@types/react": "^19.1.9",
25-
"@types/react-dom": "^19.1.7",
2624
"@vitejs/plugin-react": "^4.7.0",
2725
"dotenv": "^17.2.1",
2826
"sass": "^1.89.2",
29-
"typescript": "^5.8.3",
3027
"vite": "^7.0.6",
3128
"vite-plugin-environment": "^1.1.3"
3229
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { useInternetIdentity } from "ic-use-internet-identity";
2+
import { CollectionClaim } from "./components/CollectionClaim";
3+
import { MintNFT } from "./components/MintNFT";
4+
import { OwnedNFTs } from "./components/OwnedNFTs";
5+
import { AuthButton } from "./components/AuthButton";
6+
import { Heart } from "lucide-react";
7+
8+
function App() {
9+
const { identity, isInitializing } = useInternetIdentity();
10+
11+
if (isInitializing) {
12+
return (
13+
<div className="min-h-screen bg-gray-900 flex items-center justify-center">
14+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-purple-500"></div>
15+
</div>
16+
);
17+
}
18+
19+
return (
20+
<div className="min-h-screen bg-gray-900 text-white">
21+
<div className="container mx-auto px-4 sm:px-6 lg:px-8 py-4 sm:py-8">
22+
{/* Header */}
23+
<div className="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6 sm:mb-8">
24+
<h1 className="text-2xl sm:text-3xl font-bold bg-gradient-to-r from-purple-400 to-pink-400 bg-clip-text text-transparent text-center sm:text-left">
25+
NFT Collection Manager
26+
</h1>
27+
<div className="flex justify-center sm:justify-end">
28+
<AuthButton />
29+
</div>
30+
</div>
31+
32+
{identity ? (
33+
<div className="space-y-6 sm:space-y-8">
34+
{/* Collection Management */}
35+
<div className="bg-gray-800 rounded-lg p-4 sm:p-6 border border-gray-700">
36+
<h2 className="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 text-purple-300">
37+
Collection Management
38+
</h2>
39+
<CollectionClaim />
40+
</div>
41+
42+
{/* Minting Section */}
43+
<div className="bg-gray-800 rounded-lg p-4 sm:p-6 border border-gray-700">
44+
<h2 className="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 text-purple-300">
45+
Mint NFT
46+
</h2>
47+
<MintNFT />
48+
</div>
49+
50+
{/* Owned NFTs */}
51+
<div className="bg-gray-800 rounded-lg p-4 sm:p-6 border border-gray-700">
52+
<h2 className="text-lg sm:text-xl font-semibold mb-3 sm:mb-4 text-purple-300">
53+
Your NFTs
54+
</h2>
55+
<OwnedNFTs />
56+
</div>
57+
</div>
58+
) : (
59+
<div className="text-center py-8 sm:py-16">
60+
<div className="bg-gray-800 rounded-lg p-6 sm:p-8 max-w-sm sm:max-w-md mx-auto border border-gray-700">
61+
<h2 className="text-xl sm:text-2xl font-semibold mb-3 sm:mb-4">
62+
Welcome to NFT Collection Manager
63+
</h2>
64+
<p className="text-gray-400 mb-4 sm:mb-6 text-sm sm:text-base">
65+
Please authenticate with Internet Identity to
66+
manage your NFT collection.
67+
</p>
68+
<div className="flex justify-center">
69+
<AuthButton />
70+
</div>
71+
</div>
72+
</div>
73+
)}
74+
75+
{/* Footer */}
76+
<footer className="mt-12 sm:mt-16 text-center text-gray-500 text-xs sm:text-sm px-4">
77+
<div className="flex flex-col sm:flex-row items-center justify-center gap-1">
78+
<span>
79+
© 2025. Built with{" "}
80+
<Heart className="inline w-4 h-4 text-red-500" />{" "}
81+
using
82+
</span>
83+
<a
84+
href="https://caffeine.ai"
85+
target="_blank"
86+
rel="noopener noreferrer"
87+
className="text-purple-400 hover:text-purple-300 transition-colors"
88+
>
89+
caffeine.ai
90+
</a>
91+
</div>
92+
</footer>
93+
</div>
94+
</div>
95+
);
96+
}
97+
98+
export default App;

motoko/nft-creator/frontend/src/App.tsx

Lines changed: 0 additions & 85 deletions
This file was deleted.

motoko/nft-creator/frontend/src/components/MintNFT.tsx renamed to motoko/nft-creator/frontend/src/components/MintNFT.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { useCollectionOwner, useMintNFT } from "../hooks/useQueries";
33
import { useInternetIdentity } from "ic-use-internet-identity";
44
import { Principal } from "@dfinity/principal";
55
import { Sparkles } from "lucide-react";
6+
import { useToast } from "../contexts/ToastContext";
67

78
export function MintNFT() {
89
const { identity } = useInternetIdentity();
910
const { data: collectionOwner, isLoading: isLoadingOwner } =
1011
useCollectionOwner();
1112
const { mutate: mintNFT, isPending: isMinting } = useMintNFT();
13+
const { addError } = useToast();
1214

1315
const [recipient, setRecipient] = useState("");
1416

@@ -56,6 +58,7 @@ export function MintNFT() {
5658
setRecipient("");
5759
} catch (error) {
5860
console.error("Invalid principal:", error);
61+
addError("Invalid principal: " + (error?.message || error));
5962
}
6063
};
6164

motoko/nft-creator/frontend/src/components/NFTCard.tsx renamed to motoko/nft-creator/frontend/src/components/NFTCard.jsx

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,16 @@ import { useState } from "react";
22
import { useTransferNFT } from "../hooks/useQueries";
33
import { Principal } from "@dfinity/principal";
44
import { Send, Image as ImageIcon } from "lucide-react";
5-
import type { Metadata } from "../types";
5+
import { useToast } from "../contexts/ToastContext";
66

7-
interface NFTData {
8-
tokenId: bigint;
9-
metadata: Metadata;
10-
}
11-
12-
interface NFTCardProps {
13-
nft: NFTData;
14-
}
15-
16-
export function NFTCard({ nft }: NFTCardProps) {
7+
export function NFTCard({ nft }) {
178
const [recipient, setRecipient] = useState("");
189
const [imageError, setImageError] = useState(false);
1910
const { mutate: transferNFT, isPending: isTransferring } = useTransferNFT();
11+
const { addError } = useToast();
2012

2113
// Helper function to get metadata value by key
22-
const getMetadataValue = (key: string): string => {
14+
const getMetadataValue = (key) => {
2315
const entry = nft.metadata[0]?.find(([k]) => k === key);
2416
if (entry && entry[1] && "Text" in entry[1]) {
2517
return entry[1].Text;
@@ -39,6 +31,7 @@ export function NFTCard({ nft }: NFTCardProps) {
3931
setRecipient("");
4032
} catch (error) {
4133
console.error("Invalid principal:", error);
34+
addError("Invalid principal: " + (error?.message || error));
4235
}
4336
};
4437

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { createContext, useContext, useState, useCallback } from "react";
2+
import { X } from "lucide-react";
3+
4+
const ToastContext = createContext(undefined);
5+
6+
export function useToast() {
7+
const context = useContext(ToastContext);
8+
if (!context) {
9+
throw new Error("useToast must be used within a ToastProvider");
10+
}
11+
return context;
12+
}
13+
14+
export function ToastProvider({ children }) {
15+
const [toasts, setToasts] = useState([]);
16+
17+
const addToast = useCallback((message, type) => {
18+
const id = Date.now().toString();
19+
const newToast = { id, message, type };
20+
21+
setToasts((prev) => [newToast, ...prev]); // Latest first
22+
23+
// Auto remove after 5 seconds
24+
setTimeout(() => {
25+
removeToast(id);
26+
}, 5000);
27+
}, []);
28+
29+
const removeToast = useCallback((id) => {
30+
setToasts((prev) => prev.filter((toast) => toast.id !== id));
31+
}, []);
32+
33+
const addError = useCallback(
34+
(message) => addToast(message, "error"),
35+
[addToast]
36+
);
37+
const addSuccess = useCallback(
38+
(message) => addToast(message, "success"),
39+
[addToast]
40+
);
41+
const addInfo = useCallback(
42+
(message) => addToast(message, "info"),
43+
[addToast]
44+
);
45+
46+
return (
47+
<ToastContext.Provider
48+
value={{
49+
toasts,
50+
addToast,
51+
removeToast,
52+
addError,
53+
addSuccess,
54+
addInfo,
55+
}}
56+
>
57+
{children}
58+
<ToastContainer toasts={toasts} onRemove={removeToast} />
59+
</ToastContext.Provider>
60+
);
61+
}
62+
63+
function ToastContainer({ toasts, onRemove }) {
64+
return (
65+
<div className="fixed bottom-4 left-4 z-50 space-y-2">
66+
{toasts.map((toast, index) => (
67+
<div
68+
key={toast.id}
69+
className={`
70+
flex items-center gap-3 p-4 rounded-lg shadow-lg max-w-md
71+
transform transition-all duration-300 ease-in-out
72+
${index > 0 ? "opacity-80" : "opacity-100"}
73+
${toast.type === "error" ? "bg-red-600 text-white" : ""}
74+
${toast.type === "success" ? "bg-green-600 text-white" : ""}
75+
${toast.type === "info" ? "bg-blue-600 text-white" : ""}
76+
animate-slide-in
77+
`}
78+
style={{
79+
transform: `translateY(${index * -8}px)`,
80+
zIndex: 50 - index,
81+
}}
82+
>
83+
<div className="flex-1 text-sm font-medium">
84+
{toast.message}
85+
</div>
86+
<button
87+
onClick={() => onRemove(toast.id)}
88+
className="flex-shrink-0 p-1 hover:bg-black/20 rounded transition-colors"
89+
aria-label="Close notification"
90+
>
91+
<X className="w-4 h-4" />
92+
</button>
93+
</div>
94+
))}
95+
</div>
96+
);
97+
}

0 commit comments

Comments
 (0)