Skip to content

Commit 7a95cbe

Browse files
authored
fix: challenge 0 inputref ipfs upload error (#261)
1 parent b66e197 commit 7a95cbe

7 files changed

Lines changed: 2676 additions & 2405 deletions

File tree

packages/nextjs/app/ipfsDownload/page.tsx

Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,109 @@
11
"use client";
22

3-
import { lazy, useEffect, useState } from "react";
3+
import { useEffect, useState } from "react";
44
import type { NextPage } from "next";
55
import { notification } from "~~/utils/scaffold-stark/notification";
66
import { getMetadataFromIPFS } from "~~/utils/simpleNFT/ipfs-fetch";
77
import { INITIAL_ATTEMPT, MAX_ATTEMPTS } from "~~/utils/simpleNFT/constants";
88

9-
const LazyReactJson = lazy(() => import("react-json-view"));
9+
// Import the JSON editor component and its core CSS
10+
import { JsonEditor as Editor } from "jsoneditor-react";
11+
1012

1113
const IpfsDownload: NextPage = () => {
12-
const [yourJSON, setYourJSON] = useState({});
14+
const [yourJSON, setYourJSON] = useState<object | null>(null);
1315
const [ipfsPath, setIpfsPath] = useState("");
1416
const [loading, setLoading] = useState(false);
1517
const [mounted, setMounted] = useState(false);
18+
// In your state
19+
const [editorKey, setEditorKey] = useState(0);
1620
useEffect(() => {
1721
setMounted(true);
18-
}, []);
19-
22+
console.log("Updated JSON to editor:", yourJSON);
23+
}, [yourJSON]);
2024

2125
const handleIpfsDownload = async () => {
2226
setLoading(true);
2327
const loadingNotificationId = notification.loading("Getting data from IPFS...");
2428
let retryNotificationId = null;
2529
let attempt = INITIAL_ATTEMPT;
2630
const maxAttempts = MAX_ATTEMPTS;
31+
2732
while (attempt < maxAttempts) {
2833
try {
2934
const metaData = await getMetadataFromIPFS(ipfsPath);
35+
3036
notification.remove(loadingNotificationId);
31-
retryNotificationId && notification.remove(retryNotificationId);
37+
if (retryNotificationId) notification.remove(retryNotificationId);
3238
notification.success("Downloaded from IPFS");
33-
setYourJSON(metaData);
39+
40+
// Fix: Ensure it's serializable
41+
const cleanData = JSON.parse(JSON.stringify(metaData));
42+
console.log("meta-data:", cleanData);
43+
44+
setYourJSON(cleanData);
45+
setEditorKey(prevKey => prevKey + 1);
3446
break;
3547
} catch (error) {
3648
attempt++;
3749
if (attempt < maxAttempts) {
3850
retryNotificationId = notification.info(`Retrying download... (${attempt}/${maxAttempts})`);
3951
} else {
4052
notification.remove(loadingNotificationId);
41-
retryNotificationId && notification.remove(retryNotificationId);
53+
if (retryNotificationId) notification.remove(retryNotificationId);
4254
notification.error(error instanceof Error ? error.message : "Error downloading from IPFS");
4355
console.error("IPFS Download Error:", error);
4456
}
4557
}
4658
}
59+
4760
setLoading(false);
4861
};
4962

5063
return (
51-
<>
52-
<div className="flex items-center flex-col flex-grow pt-10">
53-
<h1 className="text-center mb-4">
54-
<span className="block text-4xl font-bold">Download from IPFS</span>
55-
</h1>
56-
<div
57-
className={`flex border-2 border-accent/95 bg-base-200 rounded-full text-accent w-96`}
58-
>
59-
<input
60-
className="input input-ghost focus:outline-none focus:bg-transparent focus:text-secondary-content h-[2.2rem] min-h-[2.2rem] px-4 border w-full font-medium placeholder:text-accent/50 text-secondary-content/75"
61-
placeholder="IPFS CID"
62-
value={ipfsPath}
63-
onChange={(e) => setIpfsPath(e.target.value)}
64-
autoComplete="off"
65-
/>
66-
</div>
67-
<button
68-
className={`btn btn-secondary text-white my-6 ${loading ? "loading" : ""}`}
69-
disabled={loading}
70-
onClick={handleIpfsDownload}
71-
>
72-
Download from IPFS
73-
</button>
64+
<div className="flex items-center flex-col flex-grow pt-10">
65+
<h1 className="text-center mb-4">
66+
<span className="block text-4xl font-bold">Download from IPFS</span>
67+
</h1>
7468

75-
{mounted && (
76-
<LazyReactJson
77-
style={{ padding: "1rem", borderRadius: "0.75rem" }}
78-
src={yourJSON}
79-
theme="solarized"
80-
enableClipboard={false}
81-
onEdit={(edit) => {
82-
setYourJSON(edit.updated_src);
83-
}}
84-
onAdd={(add) => {
85-
setYourJSON(add.updated_src);
86-
}}
87-
onDelete={(del) => {
88-
setYourJSON(del.updated_src);
89-
}}
90-
/>
91-
)}
69+
<div className={`flex border-2 border-accent/95 bg-base-200 rounded-full text-accent w-96`}>
70+
<input
71+
className="input input-ghost focus:outline-none focus:bg-transparent focus:text-secondary-content h-[2.2rem] min-h-[2.2rem] px-4 border w-full font-medium placeholder:text-accent/50 text-secondary-content/75"
72+
placeholder="IPFS CID"
73+
value={ipfsPath}
74+
onChange={(e) => setIpfsPath(e.target.value)}
75+
autoComplete="off"
76+
/>
9277
</div>
93-
</>
78+
79+
<button
80+
className={`btn btn-secondary text-white my-6 ${loading ? "loading" : ""}`}
81+
disabled={loading}
82+
onClick={handleIpfsDownload}
83+
>
84+
Download from IPFS
85+
</button>
86+
87+
{mounted && yourJSON && Object.keys(yourJSON).length > 0 && (
88+
<div style={{ width: "100%", maxWidth: "1000px" }}>
89+
<Editor
90+
key={editorKey} // Add this line
91+
value={yourJSON}
92+
onChange={(updatedJson: object) => setYourJSON(updatedJson)}
93+
mode="tree"
94+
modes={["tree", "code", "form", "text", "view"]}
95+
htmlElementProps={{
96+
style: {
97+
height: "500px",
98+
borderRadius: "0.75rem",
99+
border: "1px solid #ccc",
100+
overflow: "hidden",
101+
},
102+
}}
103+
/>
104+
</div>
105+
)}
106+
</div>
94107
);
95108
};
96109

packages/nextjs/app/ipfsUpload/page.tsx

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
"use client";
22

3-
import { lazy, useEffect, useState } from "react";
3+
import { useEffect, useState } from "react";
44
import type { NextPage } from "next";
55
import { notification } from "~~/utils/scaffold-stark/notification";
66
import { addToIPFS } from "~~/utils/simpleNFT/ipfs-fetch";
77
import nftsMetadata from "~~/utils/simpleNFT/nftsMetadata";
88
import { INITIAL_ATTEMPT, MAX_ATTEMPTS } from "~~/utils/simpleNFT/constants";
99

10-
const LazyReactJson = lazy(() => import("react-json-view"));
10+
// Import the JSON editor component and its core CSS
11+
import { JsonEditor as Editor } from 'jsoneditor-react';
12+
13+
14+
1115

1216
const IpfsUpload: NextPage = () => {
1317
const [yourJSON, setYourJSON] = useState<object>(nftsMetadata[0]);
1418
const [loading, setLoading] = useState(false);
1519
const [uploadedIpfsPath, setUploadedIpfsPath] = useState("");
1620
const [mounted, setMounted] = useState(false);
21+
1722
useEffect(() => {
1823
setMounted(true);
19-
}, []);
24+
25+
}, []);
2026

2127
const handleIpfsUpload = async () => {
2228
setLoading(true);
@@ -53,29 +59,33 @@ const IpfsUpload: NextPage = () => {
5359
</h1>
5460

5561
{mounted && (
56-
<LazyReactJson
57-
style={{ padding: "1rem", borderRadius: "0.75rem" }}
58-
src={yourJSON}
59-
theme="solarized"
60-
enableClipboard={false}
61-
onEdit={(edit) => {
62-
setYourJSON(edit.updated_src);
63-
}}
64-
onAdd={(add) => {
65-
setYourJSON(add.updated_src);
66-
}}
67-
onDelete={(del) => {
68-
setYourJSON(del.updated_src);
69-
}}
70-
/>
62+
<div style={{ width: '100%', maxWidth: '1000px' }}>
63+
<Editor
64+
value={yourJSON}
65+
onChange={(updatedJson: object) => setYourJSON(updatedJson)}
66+
mode="tree"
67+
modes={['tree', 'code', 'form', 'text', 'view']}
68+
htmlElementProps={{
69+
style: {
70+
height: '500px',
71+
borderRadius: "0.75rem",
72+
border: "1px solid #ccc", // Keep a subtle border for the container
73+
overflow: 'hidden'
74+
},
75+
76+
}}
77+
/>
78+
</div>
7179
)}
80+
7281
<button
7382
className={`btn btn-secondary text-white my-4 ${loading ? "loading" : ""}`}
7483
disabled={loading}
7584
onClick={handleIpfsUpload}
7685
>
7786
Upload to IPFS
7887
</button>
88+
7989
{uploadedIpfsPath && (
8090
<div className="mt-4">
8191
<a
@@ -92,4 +102,4 @@ const IpfsUpload: NextPage = () => {
92102
);
93103
};
94104

95-
export default IpfsUpload;
105+
export default IpfsUpload;

packages/nextjs/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
"ethers": "^6.12.0",
3737
"get-starknet-core": "^4.0.0",
3838
"ipfs-utils": "^9.0.14",
39+
"jsoneditor": "^10.2.0",
40+
"jsoneditor-react": "^3.1.2",
3941
"kubo-rpc-client": "^4.0.1",
4042
"next": "15.2.4",
4143
"next-pwa": "^5.6.0",
@@ -48,7 +50,6 @@
4850
"react-copy-to-clipboard": "^5.1.0",
4951
"react-dom": "19.0.0",
5052
"react-hot-toast": "^2.4.1",
51-
"react-json-view": "^1.21.3",
5253
"starknet": "^7.1.0",
5354
"type-fest": "^4.6.0",
5455
"usehooks-ts": "^2.13.0",
@@ -61,7 +62,7 @@
6162
"@types/next-pwa": "^5",
6263
"@types/node": "^20",
6364
"@types/nprogress": "^0",
64-
"@types/react": "19.0.12",
65+
"@types/react": "^19.1.8",
6566
"@types/react-copy-to-clipboard": "^5.0.4",
6667
"@types/react-dom": "19.0.4",
6768
"@vitejs/plugin-react": "^4.3.4",

packages/nextjs/styles/globals.css

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,56 @@ body {
151151
clip-path: polygon(100% 100%, 100% 0, 90% 100%);
152152
}
153153
}
154+
155+
156+
157+
.jsoneditor-search input {
158+
background-color: rgba(255, 255, 255, 0.1) !important;
159+
border: 1px solid rgba(255, 255, 255, 0.3) !important;
160+
color:rgb(2, 1, 1) !important;
161+
border-radius: 4px !important;
162+
padding: 4px 8px !important;
163+
}
164+
165+
.jsoneditor-results {
166+
background-color:rgba(56,131,250,255) !important;
167+
}
168+
169+
/* Fix the alignment and remove inconsistent margins */
170+
.jsoneditor-search {
171+
margin: 0 !important;
172+
padding: 8px !important;
173+
border-bottom: none !important;
174+
}
175+
176+
.jsoneditor-results {
177+
background-color: rgba(56,131,250,255) !important;
178+
margin-top: 0 !important;
179+
padding-right: 12px !important; /* Add internal padding on the right */
180+
border-top: none !important;
181+
}
182+
183+
/* Make sure the toolbar and results are flush */
184+
.jsoneditor-outer .jsoneditor-search,
185+
.jsoneditor-outer .jsoneditor-results {
186+
vertical-align: top !important;
187+
}
188+
189+
/* Remove any default margins that might be causing the gap */
190+
.jsoneditor-results-header,
191+
.jsoneditor-frame {
192+
margin-top: 0 !important;
193+
padding-top: 0 !important;
194+
}
195+
196+
/* Ensure consistent background and no gaps */
197+
.jsoneditor-search,
198+
.jsoneditor-results {
199+
background-color: #6ba3d6 !important; /* Use the same blue color */
200+
border: none !important;
201+
}
202+
203+
/* Remove any box model issues */
204+
* {
205+
box-sizing: border-box !important;
206+
}

packages/nextjs/tsconfig.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,16 @@
2121
{
2222
"name": "next"
2323
}
24-
]
24+
],
25+
// --- ADD THIS LINE ---
26+
"typeRoots": ["./node_modules/@types", "./types"] // Include your custom types folder
2527
},
26-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
28+
"include": [
29+
"next-env.d.ts",
30+
"**/*.ts",
31+
"**/*.tsx",
32+
".next/types/**/*.ts",
33+
"types/**/*.d.ts" // --- ADD THIS LINE --- to explicitly include d.ts files in your 'types' folder
34+
],
2735
"exclude": ["node_modules"]
28-
}
36+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// src/types/jsoneditor-react.d.ts
2+
3+
declare module 'jsoneditor-react' {
4+
import * as React from 'react';
5+
import { JSONEditorMode, JSONEditorOptions } from 'jsoneditor'; // Import types from the core jsoneditor library
6+
7+
interface JsonEditorProps {
8+
value?: object | null;
9+
mode?: JSONEditorMode;
10+
modes?: JSONEditorMode[];
11+
onChange?: (json: object) => void;
12+
onModeChange?: (mode: JSONEditorMode) => void;
13+
onError?: (error: Error) => void;
14+
// You can add more props that jsoneditor-react supports here as needed
15+
// For example, if it passes through all JSONEditorOptions:
16+
htmlElementProps?: React.HTMLAttributes<HTMLDivElement>;
17+
className?: string; // If it supports className directly
18+
// Any other props that the library's documentation indicates
19+
tag?: string; // if it allows changing the root element tag
20+
// If it passes jsoneditor options directly, you can extend JSONEditorOptions
21+
// E.g., options?: JSONEditorOptions;
22+
}
23+
24+
// This declares a React component that takes JsonEditorProps
25+
export class JsonEditor extends React.Component<JsonEditorProps, any> {}
26+
27+
// If it's a default export (which it often is for `jsoneditor-react`):
28+
export default JsonEditor;
29+
}

0 commit comments

Comments
 (0)