Skip to content

Commit

Permalink
feat: create new proposal page
Browse files Browse the repository at this point in the history
  • Loading branch information
0xExp-po committed Nov 22, 2024
1 parent 4d51111 commit a2e6e2a
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 0 deletions.
1 change: 1 addition & 0 deletions dapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"@astrojs/tailwind": "5.1.2",
"@creit.tech/sorobandomains-sdk": "^0.1.4",
"@creit.tech/stellar-wallets-kit": "^1.2.5",
"@mdxeditor/editor": "^3.19.2",
"@nanostores/react": "^0.8.0",
"@stellar/stellar-sdk": "^13.0.0",
"astro": "4.16.13",
Expand Down
11 changes: 11 additions & 0 deletions dapp/src/components/page/governance/GovernancePageTitle.astro
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Topic from "../../utils/Topic.astro";
</div>

<script>
import { navigate } from "astro:transitions/client";
import { projectNameForGovernance } from "utils/store";
import { capitalizeFirstLetter } from "utils/utils";

Expand All @@ -32,5 +33,15 @@ import Topic from "../../utils/Topic.astro";
}
}
});

const submitProposalButton = document.getElementById(
"wrap-create-proposal-button",
);

if (submitProposalButton) {
submitProposalButton.addEventListener("click", () => {
navigate(`/proposal/new`);
});
}
});
</script>
13 changes: 13 additions & 0 deletions dapp/src/components/page/governance/NewProposalPage.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
import ProposalForm from "./ProposalForm";
---

<div
class="relative flex flex-col items-center lg:flex-row my-6 bg-zinc-100 rounded-[45px]"
>
<div
class="row items-center py-12 px-4 sm:px-12 md:px-20 md:py-10 w-full lg:max-w-none"
>
<ProposalForm client:load />
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
import Topic from "components/utils/Topic.astro";
---

<div
class="relative flex flex-col items-center md:flex-row justify-between mb-1.5 mt-4 sm:mt-8 md:mt-12 gap-y-4"
>
<Topic title="Create Proposal" description="" id="new-proposal-topic" />
</div>
291 changes: 291 additions & 0 deletions dapp/src/components/page/governance/ProposalForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
import React from "react";
import { useState, useEffect } from "react";
import {
MDXEditor,
headingsPlugin,
listsPlugin,
quotePlugin,
thematicBreakPlugin,
markdownShortcutPlugin,
linkPlugin,
linkDialogPlugin,
imagePlugin,
tablePlugin,
codeBlockPlugin,
codeMirrorPlugin,
diffSourcePlugin,
frontmatterPlugin,
toolbarPlugin,
Separator,
UndoRedo,
BoldItalicUnderlineToggles,
BlockTypeSelect,
CodeToggle,
CreateLink,
DiffSourceToggleWrapper,
InsertAdmonition,
InsertImage,
InsertTable,
InsertFrontmatter,
InsertThematicBreak,
InsertCodeBlock,
ListsToggle,
type CodeBlockEditorDescriptor,
useCodeBlockEditorContext,
} from "@mdxeditor/editor";
import "@mdxeditor/editor/style.css";
import { capitalizeFirstLetter } from "utils/utils";
import type { ProposalOutcome } from "types/proposal";

const ProposalForm: React.FC = () => {
const [mdText, setMdText] = useState("");
const [isClient, setIsClient] = useState(false);
const [approveDescription, setApproveDescription] = useState("");
const [rejectDescription, setRejectDescription] = useState("");
const [cancelledDescription, setCancelledDescription] = useState("");
const [approveXdr, setApproveXdr] = useState("");
const [rejectXdr, setRejectXdr] = useState("");
const [cancelledXdr, setCancelledXdr] = useState("");
const [imageFiles, setImageFiles] = useState<
{ localUrl: string; publicUrl: string; source: File }[]
>([]);

useEffect(() => {
setIsClient(true);
}, []);

if (!isClient) {
return null;
}

const imageUploadHandler = async (image: File) => {
const url = URL.createObjectURL(image);
setImageFiles([
...imageFiles,
{ localUrl: url, publicUrl: image.name, source: image },
]);
console.log("imageUrl:", imageFiles);
return url;
};

const PlainTextCodeEditorDescriptor: CodeBlockEditorDescriptor = {
match: (language, meta) => true,
priority: 0,
Editor: (props) => {
const cb = useCodeBlockEditorContext();
return (
<div onKeyDown={(e) => e.nativeEvent.stopImmediatePropagation()}>
<textarea
rows={5}
cols={20}
defaultValue={props.code}
onChange={(e) => cb.setCode(e.target.value)}
/>
</div>
);
},
};

const submitProposal = () => {
const proposalOutcome: ProposalOutcome = {
approved: {
description: approveDescription,
xdr: approveXdr,
},
rejected: {
description: rejectDescription,
xdr: rejectXdr,
},
cancelled: {
description: cancelledDescription,
xdr: cancelledXdr,
},
};
const outcome = new Blob([JSON.stringify(proposalOutcome)], {
type: "application/json",
});

let files = [new File([outcome], "outcomes.json")];
let description = mdText;

imageFiles.forEach((image) => {
if (description.includes(image.localUrl)) {
description = description.replace(
new RegExp(image.localUrl, "g"),
image.publicUrl,
);
files.push(new File([image.source], image.publicUrl));
}
});

files.push(new File([description], "proposal.md"));
alert("Proposal submitted successfully!");
};

return (
<div>
<h3 className="text-base sm:text-lg md:text-2xl font-semibold py-2">
Proposal Description
</h3>
<div className="rounded-md border border-zinc-700 overflow-hidden">
<MDXEditor
plugins={[
markdownShortcutPlugin(),
headingsPlugin(),
listsPlugin(),
quotePlugin(),
thematicBreakPlugin(),
linkPlugin(),
linkDialogPlugin(),
imagePlugin({
imageUploadHandler,
}),
tablePlugin(),
codeBlockPlugin({
defaultCodeBlockLanguage: "ts",
codeBlockEditorDescriptors: [PlainTextCodeEditorDescriptor],
}),
codeMirrorPlugin({
codeBlockLanguages: {
js: "JavaScript",
css: "CSS",
rust: "Rust",
html: "HTML",
json: "JSON",
ts: "TypeScript",
},
}),
diffSourcePlugin({
diffMarkdown: "boo",
}),
frontmatterPlugin(),
toolbarPlugin({
toolbarClassName: "my-classname",
toolbarContents: () => (
<>
<UndoRedo />
<Separator />
<BoldItalicUnderlineToggles />
<CodeToggle />
<BlockTypeSelect />
<Separator />
<ListsToggle />
<Separator />
<CreateLink />
<InsertImage />
<InsertTable />
<Separator />
<InsertAdmonition />
<InsertThematicBreak />
<InsertCodeBlock />
<Separator />
<InsertFrontmatter />
<DiffSourceToggleWrapper>
<div></div>
</DiffSourceToggleWrapper>
<BoldItalicUnderlineToggles />
</>
),
}),
]}
markdown={mdText}
onChange={(value) => setMdText(value || "")}
placeholder="Input your proposal description here..."
/>
</div>
<h3 className="text-base sm:text-lg md:text-2xl font-semibold py-2">
Proposal Outcome
</h3>
<div className="w-full max-w-[840px] mx-auto flex flex-col gap-4 sm:gap-6 md:gap-10">
<OutcomeInput
type="approved"
description={approveDescription}
setDescription={setApproveDescription}
xdr={approveXdr}
setXdr={setApproveXdr}
/>
<OutcomeInput
type="rejected"
description={rejectDescription}
setDescription={setRejectDescription}
xdr={rejectXdr}
setXdr={setRejectXdr}
/>
<OutcomeInput
type="cancelled"
description={cancelledDescription}
setDescription={setCancelledDescription}
xdr={cancelledXdr}
setXdr={setCancelledXdr}
/>
<div className="ml-[80px] sm:ml-[90px] md:ml-[105px]">
<button
className="w-full py-5 bg-zinc-900 rounded-[14px] justify-center gap-2.5 inline-flex"
onClick={() => submitProposal()}
>
<span className="text-center text-white text-xl font-normal leading-7">
Submit Proposal
</span>
</button>
</div>
</div>
</div>
);
};

export default ProposalForm;

interface OutcomeInputProps {
type: string;
description: string;
setDescription: React.Dispatch<React.SetStateAction<string>>;
xdr: string;
setXdr: React.Dispatch<React.SetStateAction<string>>;
}

const OutcomeInput = ({
type,
description,
setDescription,
xdr,
setXdr,
}: OutcomeInputProps) => {
return (
<div className="flex flex-col gap-2 sm:gap-3 md:gap-4">
<div className="w-min">
<div
className={`text-sm sm:text-base md:text-lg text-white md:py-0.5 px-1 md:px-2 rounded-sm md:rounded
${type === "approved" ? "bg-approved" : type === "rejected" ? "bg-conflict" : type === "cancelled" && "bg-abstain"}`}
>
{capitalizeFirstLetter(type)}
</div>
</div>
<div className="flex flex-col gap-3 sm:gap-4 md:gap-6">
<div className="flex items-start">
<div className="w-[80px] sm:w-[90px] md:w-[105px] pr-1.5 flex justify-end">
<div className="text-sm sm:text-base">Description:</div>
</div>
<textarea
value={description}
onChange={(e) => {
setDescription(e.target.value);
}}
className="w-[calc(100%-80px)] sm:w-[calc(100%-90px)] md:w-[calc(100%-105px)] h-max text-sm sm:text-base rounded-md border border-zinc-700 outline-none resize-y"
/>
</div>
<div className="flex items-start">
<div className="w-[80px] sm:w-[90px] md:w-[105px] pr-1.5 flex justify-end">
<div className="text-sm sm:text-base">XDR:</div>
</div>
<textarea
value={xdr}
onChange={(e) => {
setXdr(e.target.value);
}}
className="w-[calc(100%-80px)] sm:w-[calc(100%-90px)] md:w-[calc(100%-105px)] h-max text-sm sm:text-base rounded-md border border-zinc-700 outline-none resize-y"
/>
</div>
</div>
</div>
);
};
23 changes: 23 additions & 0 deletions dapp/src/pages/proposal/new.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
import Layout from "../../layouts/Layout.astro";
import Container from "../../components/layout/Container.astro";
import NewProposalPageTitle from "components/page/governance/NewProposalPageTitle.astro";
import NewProposalPage from "components/page/governance/NewProposalPage.astro";
---

<script is:inline>
var global = global || window;
</script>

<Layout title="Tansu">
<main class="space-y-20">
<Container>
<NewProposalPageTitle />
<NewProposalPage />
</Container>
</main>
</Layout>

<script>
document.addEventListener("astro:page-load", async () => {});
</script>

0 comments on commit a2e6e2a

Please sign in to comment.