Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Karma GAP SDK integration #3675

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "builder",
"version": "0.1.0",
"private": true,
"engines": {
"engines": {
"npm": ">=8.5.5",
"node": ">=20.1.0"
},
Expand All @@ -16,6 +16,7 @@
"@datadog/browser-rum": "^4.16.0",
"@ethersproject/address": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"gap": "file:../gap",
"@gitcoinco/passport-sdk-types": "^0.2.0",
"@headlessui/react": "^1.6.5",
"@heroicons/react": "^2.0.11",
Expand Down
321 changes: 321 additions & 0 deletions packages/builder/src/components/application/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Form.tsx
import { Stack } from "@chakra-ui/react";
import { datadogRum } from "@datadog/browser-rum";
import { getConfig } from "common/src/config";
Expand All @@ -16,6 +17,7 @@ import {
RoundApplicationMetadata,
} from "data-layer/dist/roundApplication.types";
import { getChainById, useValidateCredential } from "common";
import { useGap } from "gap";
import { Fragment, useEffect, useState } from "react";
import { shallowEqual, useDispatch, useSelector } from "react-redux";
import { Link } from "react-router-dom";
Expand Down Expand Up @@ -373,6 +375,136 @@ export default function Form({
const needsProject = !schema.questions.find((q) => q.type === "project");
const now = new Date().getTime() / 1000;

const {
getProjectById,
createProject,
createGrant,
createMilestone,
project,
milestones,
isGapLoading,
error,
} = useGap(process.env.REACT_APP_KARMA_GAP_INDEXER_URL!);

const [newProjectData, setNewProjectData] = useState<{
title: string;
description: string;
imageURL: string;
links?: { type: string; url: string }[];
tags?: { name: string }[];
members?: `0x${string}`[];
}>({
title: "",
description: "",
imageURL: "",
links: [],
tags: [],
members: [],
});

const [newGrantData, setNewGrantData] = useState<{
communityUID: string;
title: string;
proposalURL: string;
description?: string;
cycle?: string;
season?: string;
milestones?: Array<{
title: string;
description: string;
endsAt: number;
}>;
}>({
communityUID: "",
title: "",
proposalURL: "",
description: "",
cycle: "",
season: "",
milestones: [],
});

const [newMilestoneData, setNewMilestoneData] = useState<{
title: string;
description: string;
endsAt: number;
}>({
title: "",
description: "",
endsAt: Date.now(),
});

const handleNewProjectInputChange = (e: ChangeHandlers) => {
const { name, value } = e.target;
setNewProjectData((prevData) => ({
...prevData,
[name]: value,
}));
};

const handleNewGrantInputChange = (e: ChangeHandlers) => {
const { name, value } = e.target;
setNewGrantData((prevData) => ({
...prevData,
[name]: value,
}));
};

const handleNewMilestoneInputChange = (
e: React.ChangeEvent<
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
>
) => {
const { name, value } = e.target;
let updatedValue: string | number = value;

if (name === "endsAt") {
updatedValue = new Date(value).getTime();
}

setNewMilestoneData((prevData) => ({
...prevData,
[name]: updatedValue,
}));
};

const handleCreateMilestone = async () => {
try {
setIsLoading(true);

// Step 1: Create Project
const projectResult = await createProject(newProjectData);

// Step 2: Create Grant
const grantResult = await createGrant(
newGrantData,
projectResult.projectUID
);

// Step 3: Create Milestone
await createMilestone(newMilestoneData, grantResult.grantUID);

// After creating everything, refresh the project data
getProjectById(projectResult.projectUID);
setIsLoading(false);
} catch (err) {
console.error("Error creating milestone:", err);
setIsLoading(false);
}
};

useEffect(() => {
if (isValidProjectSelected && selectedProjectID) {
getProjectById(selectedProjectID);
}
}, [isValidProjectSelected, selectedProjectID, getProjectById]);

useEffect(() => {
if (project) {
console.log("Project found:", project);
}
}, [project]);

return (
<>
{preview && selectedProjectMetadata && (
Expand Down Expand Up @@ -455,6 +587,195 @@ export default function Form({
);
}

if (input.title === "Milestones") {
const renderMilestoneContent = () => {
if (isGapLoading) {
return <p>Loading project information...</p>;
}
if (project) {
return (
<div>
<p>Project Title: {project.title}</p>
{/* Display existing milestones if any */}
{milestones && milestones.length > 0 ? (
<div>
<h3>Existing Milestones:</h3>
<ul>
{milestones.map((milestone) => (
<li key={milestone.uid}>
<strong>{milestone.title}</strong>:
{milestone.description}
</li>
))}
</ul>
</div>
) : (
<p>No milestones found for this project.</p>
)}
{/* Form to create a new milestone */}
<h3>Create a New Milestone</h3>
<TextInput
key="newMilestoneTitle"
label="Milestone Title"
name="title"
value={newMilestoneData.title}
disabled={false}
changeHandler={handleNewMilestoneInputChange}
required
feedback={{ type: "none", message: "" }}
/>
<TextArea
key="newMilestoneDescription"
label="Milestone Description"
name="description"
value={newMilestoneData.description}
disabled={false}
changeHandler={handleNewMilestoneInputChange}
required
feedback={{ type: "none", message: "" }}
/>
{/* Date input for endsAt */}
<TextInput
key="newMilestoneEndsAt"
label="Milestone End Date"
name="endsAt"
inputType="date"
value={new Date(newMilestoneData.endsAt)
.toISOString()
.substr(0, 10)}
disabled={false}
changeHandler={handleNewMilestoneInputChange}
required
feedback={{ type: "none", message: "" }}
/>
{/* Create Milestone Button */}
<Button
variant={ButtonVariants.primary}
onClick={handleCreateMilestone}
disabled={isGapLoading}
>
Create Milestone
</Button>
</div>
);
}
if (error) {
return (
<div>
<p>No project found in Karma GAP for this application.</p>
<p>
Please enter the project details to create a new
project, grant, and milestone.
</p>
{/* Form to create a new project */}
<h3>Create a New Project</h3>
<TextInput
key="newProjectTitle"
label="Project Title"
name="title"
value={newProjectData.title}
disabled={false}
changeHandler={handleNewProjectInputChange}
required
feedback={{ type: "none", message: "" }}
/>
<TextArea
key="newProjectDescription"
label="Project Description"
name="description"
value={newProjectData.description}
disabled={false}
changeHandler={handleNewProjectInputChange}
required
feedback={{ type: "none", message: "" }}
/>
{/* Form to create a new grant */}
<h3>Create a New Grant</h3>
<TextInput
key="newGrantTitle"
label="Grant Title"
name="title"
value={newGrantData.title}
disabled={false}
changeHandler={handleNewGrantInputChange}
required
feedback={{ type: "none", message: "" }}
/>
<TextArea
key="newGrantDescription"
label="Grant Description"
name="description"
value={newGrantData.description}
disabled={false}
changeHandler={handleNewGrantInputChange}
required
feedback={{ type: "none", message: "" }}
/>
{/* Form to create a new milestone */}
<h3>Create a New Milestone</h3>
<TextInput
key="newMilestoneTitle"
label="Milestone Title"
name="title"
value={newMilestoneData.title}
disabled={false}
changeHandler={handleNewMilestoneInputChange}
required
feedback={{ type: "none", message: "" }}
/>
<TextArea
key="newMilestoneDescription"
label="Milestone Description"
name="description"
value={newMilestoneData.description}
disabled={false}
changeHandler={handleNewMilestoneInputChange}
required
feedback={{ type: "none", message: "" }}
/>
{/* Date input for endsAt */}
<TextInput
key="newMilestoneEndsAt"
label="Milestone End Date"
name="endsAt"
inputType="date"
value={new Date(newMilestoneData.endsAt)
.toISOString()
.substr(0, 10)}
disabled={false}
changeHandler={handleNewMilestoneInputChange}
required
feedback={{ type: "none", message: "" }}
/>
{/* Create Milestone Button */}
<Button
variant={ButtonVariants.primary}
onClick={handleCreateMilestone}
disabled={isGapLoading}
>
Create Milestone
</Button>
</div>
);
}
// Default case if none of the above conditions are met
return <p>Loading project information...</p>;
};
return (
<div key={input.id} className="mt-6">
<InputLabel
title={input.title}
encrypted={input.encrypted}
hidden={input.hidden}
/>
{isValidProjectSelected && selectedProjectID ? (
renderMilestoneContent()
) : (
<p>Please select a project to view or create milestones.</p>
)}
</div>
);
}
// Add isPreview for Application View when readonly
if (
(isValidProjectSelected || readOnly) &&
Expand Down
3 changes: 3 additions & 0 deletions packages/gap/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: ["gitcoin"],
};
28 changes: 28 additions & 0 deletions packages/gap/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "gap",
"version": "1.0.0",
"main": "dist/gap.bundle.js",
"types": "dist/index.d.ts",
"private": false,
"scripts": {
"build": "webpack --config webpack.config.js",
"dev": "tsc --watch"
},
"dependencies": {
"@show-karma/karma-gap-sdk": "^0.3.45",
"ethers": "^6.13.3",
"react": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.0.31",
"ts-loader": "^9.5.1",
"typescript": "^5.6.2",
"webpack": "^5",
"webpack-cli": "^5.1.4"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0",
"@typechain/ethers-v6": "^0.5.1",
"typechain": "^8.3.2"
}
}
Loading
Loading