>
@@ -563,8 +572,9 @@ function Editor() {
onDraftStateChange,
draftState:
draftState?.edit_post_id == postId ? draftState : undefined,
- toggleEditor: toggleEditor,
+ setEditorState: setEditorState,
transactionHashes: props.transactionHashes,
+ setExpandReplies,
}}
/>
>
@@ -677,7 +687,8 @@ const postsList =
class={`collapse mt-3 ${
defaultExpanded ||
childPostHasDraft ||
- state.childrenOfChildPostsHasDraft
+ state.childrenOfChildPostsHasDraft ||
+ state.expandReplies
? "show"
: ""
}`}
diff --git a/src/devhub/entity/post/PostEditor.jsx b/src/devhub/entity/post/PostEditor.jsx
index b2de4e2e5..d7570c137 100644
--- a/src/devhub/entity/post/PostEditor.jsx
+++ b/src/devhub/entity/post/PostEditor.jsx
@@ -1,3 +1,37 @@
+const { normalize } =
+ VM.require("${REPL_DEVHUB}/widget/core.lib.stringUtils") || (() => {});
+
+const CenteredMessage = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ width: 100%;
+ height: 384px;
+`;
+
+function initLabels() {
+ const labels = [];
+ if (typeof props.labels === "string") {
+ labels.push(...props.labels.split(","));
+ }
+ if (Array.isArray(props.labels)) {
+ labels.push(...props.labels);
+ }
+ if (props.referral) {
+ labels.push(`referral:${props.referral}`);
+ }
+ return labels;
+}
+
+if (!context.accountId) {
+ return (
+
+ Please sign in to create a post.
+
+ );
+}
+
const cleanDescription = (description) => {
return description
? description.replace(
@@ -7,13 +41,83 @@ const cleanDescription = (description) => {
: description;
};
-const postType = props.postType ?? "Sponsorship";
+const postTypeOptions = {
+ Idea: {
+ name: "Idea",
+ icon: "bi-lightbulb",
+ description:
+ "Get feedback from the community about a problem, opportunity, or need.",
+ },
+
+ Solution: {
+ name: "Solution",
+ icon: "bi-rocket",
+ description:
+ "Provide a specific proposal or implementation to an idea, optionally requesting funding. If your solution relates to an existing idea, please reply to the original post with a solution.",
+ },
+};
+
+let fields = {
+ Comment: ["description"],
+ Idea: ["name", "description"],
+ Solution: ["name", "description", "fund_raising"],
+ Attestation: ["name", "description"],
+ Sponsorship: [
+ "name",
+ "description",
+ "amount",
+ "sponsorship_token",
+ "supervisor",
+ ],
+ Github: ["githubLink", "name", "description"],
+};
+
+const isCreatePostPage = props.isCreatePostPage ?? false;
+const postType = props.postType ?? "Idea";
const parentId = props.parentId ?? null;
-const postId = props.postId ?? null;
const mode = props.mode ?? "Create";
-const toggleEditor = props.toggleEditor;
-const referralLabels = props.referral ? [`referral:${props.referral}`] : [];
-const labelStrings = (props.labels ?? []).concat(referralLabels);
+const labelStrings = initLabels();
+const [postIdList, setPostIdList] = useState(null); // to show updated post after approve txn
+const [showPostPage, setShowPostPage] = useState(false); // show newly created post
+const [postId, setPostId] = useState(props.postId ?? null);
+const [postData, setPostData] = useState(null); // for capturing edit post change
+
+useEffect(() => {
+ if (mode == "Edit") {
+ const data = Near.view("${REPL_DEVHUB_CONTRACT}", "get_post", {
+ post_id: postId,
+ });
+ if (!postData) {
+ setPostData(data);
+ }
+ if (postData && data && JSON.stringify(postData) !== JSON.stringify(data)) {
+ props.setEditorState(false);
+ props.setExpandReplies(true);
+ setPostData(data);
+ }
+ } else {
+ const postIds = Near.view("${REPL_DEVHUB_CONTRACT}", "get_all_post_ids");
+ if (!postIdList) {
+ setPostIdList(postIds);
+ }
+ if (
+ postIdList?.length > 0 &&
+ postIds.length > 0 &&
+ postIdList.length !== postIds.length
+ ) {
+ props.onDraftStateChange(null);
+ if (isCreatePostPage) {
+ setShowPostPage(true);
+ setPostId(postIds[postIds?.length - 1]);
+ } else {
+ // close editor and expand replies
+ props.setEditorState(false);
+ props.setExpandReplies(true);
+ }
+ setPostIdList(postIds);
+ }
+ }
+});
const labels = labelStrings.map((s) => {
return { name: s };
@@ -41,6 +145,7 @@ initState({
draftStateApplied: false,
mentionInput: "", // text next to @ tag
mentionsArray: [], // all the mentions in the description
+ displayFields: fields[postType],
});
/* INCLUDE: "core/lib/autocomplete" */
@@ -85,6 +190,12 @@ if (props.transactionHashes) {
if (is_edit_or_add_post_transaction) {
props.onDraftStateChange(null);
}
+
+ // show the latest created post to user
+ if (transaction_method_name == "add_post" && isCreatePostPage) {
+ setShowPostPage(true);
+ setPostId(postIdList?.[postIdList?.length - 1]);
+ }
}
}
@@ -135,20 +246,12 @@ if (!state.draftStateApplied && props.draftState) {
State.update({ ...props.draftState, draftStateApplied: true });
}
-let fields = {
- Comment: ["description"],
- Idea: ["name", "description"],
- Solution: ["name", "description", "fund_raising"],
- Attestation: ["name", "description"],
- Sponsorship: [
- "name",
- "description",
- "amount",
- "sponsorship_token",
- "supervisor",
- ],
- Github: ["githubLink", "name", "description"],
-}[postType];
+const typeSwitch = (optionName) => {
+ State.update({
+ postType: optionName,
+ displayFields: fields[optionName],
+ });
+};
// This must be outside onClick, because Near.view returns null at first, and when the view call finished, it returns true/false.
// If checking this inside onClick, it will give `null` and we cannot tell the result is true or false.
@@ -214,8 +317,8 @@ const onSubmit = () => {
github_version: "V0",
github_link: state.githubLink,
},
- }[postType];
- body["post_type"] = postType;
+ }[state.postType];
+ body["post_type"] = state.postType;
if (!context.accountId) {
return;
}
@@ -266,16 +369,6 @@ const onSubmit = () => {
}
};
-const normalizeLabel = (label) =>
- label
- .replaceAll(/[- \.]/g, "_")
- .replaceAll(/[^\w]+/g, "")
- .replaceAll(/_+/g, "-")
- .replace(/^-+/, "")
- .replace(/-+$/, "")
- .toLowerCase()
- .trim("-");
-
const checkLabel = (label) => {
Near.asyncView("${REPL_DEVHUB_CONTRACT}", "is_allowed_to_use_labels", {
editor: context.accountId,
@@ -297,7 +390,7 @@ const checkLabel = (label) => {
const setLabels = (labels) => {
labels = labels.map((o) => {
- o.name = normalizeLabel(o.name);
+ o.name = normalize(o.name);
return o;
});
if (labels.length < state.labels.length) {
@@ -343,7 +436,9 @@ const existingLabels = existingLabelStrings
const labelEditor = (
- Labels:
+
+ Labels
+
- Title:
+
+
+ Title
+
State.update({ name: event.target.value })}
@@ -432,8 +532,9 @@ const supervisorDiv = (
const callDescriptionDiv = () => {
return (
- Description:
-
+
+ Description
+
(Numbers Only)
0 ? state.amount : ""}
min={0}
@@ -582,140 +684,213 @@ function generateDescription(text, amount, token, supervisor, seekingFunding) {
const [tab, setTab] = useState("editor");
-const renamedPostType = postType == "Submission" ? "Solution" : postType;
+const renamedPostType =
+ state.postType == "Submission" ? "Solution" : state.postType;
// Below there is a weird code with fields.includes("githubLink") ternary operator.
// This is to hack around rendering bug of near.social.
+
return (
-
-
-
-
-
- setTab("editor")}
- >
- Editor
-
-
-
+
+
+ {showPostPage ? (
+
+ ) : (
+
+
+
+
+
+ setTab("editor")}
+ >
+ Editor
+
+
+
+ setTab("preview")}
+ >
+ Preview
+
+
+
+
+ {!isCreatePostPage && tab === "editor" && (
+
+ {mode} {renamedPostType}
+
+ )}
+ {tab === "preview" &&
Post Preview
}
+
+
+ {tab === "editor" && (
+ <>
+ {state.warning && (
+
+ {state.warning}
+ State.update({ warning: "" })}
+ >
+
+ )}
+ {isCreatePostPage && (
+
+
+ What do you want to create?
+
+
+ {Object.values(postTypeOptions).map((option) => (
+ typeSwitch(option.name)}
+ style={
+ state.postType === option.name
+ ? {
+ backgroundColor: "#0C7283",
+ color: "#f3f3f3",
+ }
+ : null
+ }
+ type="button"
+ >
+
+ {option.name}
+
+ ))}
+
+
+ {postTypeOptions[state.postType].description}
+
+
+ )}
+ {/* This statement around the githubLinkDiv creates a weird render bug
+ where the title renders extra on state change. */}
+ {state.displayFields.includes("githubLink") ? (
+
+ {state.displayFields.includes("githubLink") &&
+ githubLinkDiv}
+ {labelEditor}
+ {state.displayFields.includes("name") && nameDiv}
+ {state.displayFields.includes("description") &&
+ callDescriptionDiv()}
+
+ ) : (
+
+ {labelEditor}
+ {state.displayFields.includes("name") && nameDiv}
+ {state.displayFields.includes("amount") && amountDiv}
+ {state.displayFields.includes("sponsorship_token") &&
+ tokenDiv}
+ {state.displayFields.includes("supervisor") &&
+ supervisorDiv}
+ {state.displayFields.includes("description") &&
+ callDescriptionDiv()}
+ {state.displayFields.includes("fund_raising") &&
+ isFundraisingDiv}
+ {state.seekingFunding &&
+ state.displayFields.includes("fund_raising") &&
+ fundraisingDiv}
+
+ )}
+
+ {disclaimer}
+ >
+ )}
+ {tab === "preview" && (
+
+
+
+ )}
setTab("preview")}
+ data-testid="submit-create-post"
+ style={{
+ width: "7rem",
+ backgroundColor: "#0C7283",
+ color: "#f3f3f3",
+ }}
+ disabled={
+ (state.seekingFunding && (!state.amount || state.amount < 1)) ||
+ (isCreatePostPage &&
+ (state.name === "" || state.description === ""))
+ }
+ className="btn btn-light mb-2 p-3"
+ onClick={onSubmit}
>
- Preview
+ Submit
-
-
-
- {tab === "editor" && (
-
- {mode} {renamedPostType}
+ {!isCreatePostPage && (
+ props.setEditorState(false)}
+ >
+ Cancel
+
+ )}
+
)}
- {tab === "preview" &&
Post Preview
}
-
- {tab === "editor" && (
-
- {state.warning && (
-
- {state.warning}
- State.update({ warning: "" })}
- >
-
- )}
- {/* This statement around the githubLinkDiv creates a weird render bug
- where the title renders extra on state change. */}
- {fields.includes("githubLink") ? (
-
- {fields.includes("githubLink") && githubLinkDiv}
- {labelEditor}
- {fields.includes("name") && nameDiv}
- {fields.includes("description") && callDescriptionDiv()}
-
- ) : (
-
- {labelEditor}
- {fields.includes("name") && nameDiv}
- {fields.includes("amount") && amountDiv}
- {fields.includes("sponsorship_token") && tokenDiv}
- {fields.includes("supervisor") && supervisorDiv}
- {fields.includes("description") && callDescriptionDiv()}
- {fields.includes("fund_raising") && isFundraisingDiv}
- {state.seekingFunding &&
- fields.includes("fund_raising") &&
- fundraisingDiv}
-
- )}
-
- Submit
-
-
- Cancel
-
- {disclaimer}
-
- )}
- {tab === "preview" && (
-
-
-
- )}
);
diff --git a/src/devhub/page/community/index.jsx b/src/devhub/page/community/index.jsx
index 1c47da7ce..fc87ce817 100644
--- a/src/devhub/page/community/index.jsx
+++ b/src/devhub/page/community/index.jsx
@@ -1,3 +1,6 @@
+const { normalize } =
+ VM.require("${REPL_DEVHUB}/widget/core.lib.stringUtils") || (() => {});
+
const Button = styled.button`
height: 40px;
font-size: 14px;
@@ -46,6 +49,8 @@ if (!tab) {
tab = "Announcements";
}
+tab = normalize(tab);
+
const [isLinkCopied, setLinkCopied] = useState(false);
const tabs = [
@@ -92,7 +97,7 @@ const onShareClick = () =>
)
.then(setLinkCopied(true));
-let currentTab = tabs.find((it) => it.title === tab);
+let currentTab = tabs.find((it) => normalize(it.title) === tab);
const CommunityName = styled.span`
color: #151515;
@@ -291,13 +296,13 @@ return (
params: {
page: "community",
handle: community.handle,
- tab: title,
+ tab: normalize(title),
},
})}
- aria-current={tab === title && "page"}
+ aria-current={tab === normalize(title) && "page"}
className={[
"d-inline-flex gap-2",
- tab === title ? "nav-link active" : "nav-link",
+ tab === normalize(title) ? "nav-link active" : "nav-link",
].join(" ")}
>
{title}
diff --git a/src/devhub/page/contribute.jsx b/src/devhub/page/contribute.jsx
index bc7a102dd..6e1be1322 100644
--- a/src/devhub/page/contribute.jsx
+++ b/src/devhub/page/contribute.jsx
@@ -66,7 +66,7 @@ const actions = [
description:
"We are always on the lookout for events that align with our mission and provide value to the NEAR ecosystem. If you are organizing such an event, we would love to hear from you! Below is a guide on how to submit a sponsorship proposal to us.",
ctaAction: "Learn More →",
- ctaLink: "/devhub.near/widget/app?page=community&handle=hacks&tab=Wiki%202",
+ ctaLink: "/devhub.near/widget/app?page=community&handle=hacks&tab=wiki-202",
},
{
title: "Improve NEAR Docs",
@@ -81,7 +81,7 @@ const actions = [
"As the NEAR ecosystem grows rapidly, there is an increasing need to improve developer productivity. The DevDAO NEAR Platform Fellowship Program aims to solve this issue by providing guidance to new contributors from experienced developers.",
ctaAction: "Learn More →",
ctaLink:
- "/devhub.near/widget/app?page=community&handle=fellowship&tab=Wiki 1",
+ "/devhub.near/widget/app?page=community&handle=fellowship&tab=wiki-1",
},
{
title: "Join NEAR Campus",
diff --git a/src/devhub/page/create.jsx b/src/devhub/page/create.jsx
deleted file mode 100644
index 5a78e3412..000000000
--- a/src/devhub/page/create.jsx
+++ /dev/null
@@ -1,641 +0,0 @@
-const CenteredMessage = styled.div`
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- width: 100%;
- height: 384px;
-`;
-
-if (!context.accountId) {
- return (
-
- Please sign in to create a post.
-
- );
-}
-
-const postTypeOptions = {
- Idea: {
- name: "Idea",
- icon: "bi-lightbulb",
-
- description:
- "Get feedback from the community about a problem, opportunity, or need.",
- },
-
- Solution: {
- name: "Solution",
- icon: "bi-rocket",
-
- description:
- "Provide a specific proposal or implementation to an idea, optionally requesting funding. If your solution relates to an existing idea, please reply to the original post with a solution.",
- },
-};
-
-const typeSwitch = (optionName) => {
- State.update({
- postType: optionName,
- });
-};
-
-function initLabels() {
- const labels = [];
- if (props.labels) {
- labels.push(...props.labels.split(","));
- }
- if (props.referral) {
- labels.push(`referral:${props.referral}`);
- }
- return labels;
-}
-
-State.init({
- seekingFunding: false,
- labels: initLabels(),
- postType: "Idea",
- name: props.name ?? "",
- description: props.description ?? "",
- amount: props.amount ?? "",
- token: props.token ?? "USDT",
- supervisor: props.supervisor ?? "neardevdao.near",
- warning: "",
- mentionInput: "", // text next to @ tag
- mentionsArray: [], // all the mentions in the description
-});
-
-const autocompleteEnabled = true;
-
-const AutoComplete = styled.div`
- z-index: 5;
-
- > div > div {
- padding: calc(var(--padding) / 2);
- }
-`;
-
-function textareaInputHandler(value) {
- const words = value.split(/\s+/);
- const allMentions = words
- .filter((word) => word.startsWith("@"))
- .map((mention) => mention.slice(1));
- const newMentions = allMentions.filter(
- (item) => !state.mentionsArray.includes(item)
- );
-
- State.update({
- text: value,
- showAccountAutocomplete: newMentions?.length > 0,
- mentionsArray: allMentions,
- mentionInput: newMentions?.[0] ?? "",
- });
-}
-
-function autoCompleteAccountId(id) {
- // to make sure we update the @ at correct index
- let currentIndex = 0;
- const updatedDescription = state.description.replace(
- /(?:^|\s)(@[^\s]*)/g,
- (match) => {
- if (currentIndex === state.mentionsArray.indexOf(state.mentionInput)) {
- currentIndex++;
- return ` @${id}`;
- } else {
- currentIndex++;
- return match;
- }
- }
- );
-
- State.update({
- handler: "autocompleteSelected",
- description: updatedDescription,
- showAccountAutocomplete: false,
- });
-}
-
-const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url");
-
-if (!href) {
- return Loading modules...
;
-}
-
-// This must be outside onClick, because Near.view returns null at first, and when the view call finished, it returns true/false.
-// If checking this inside onClick, it will give `null` and we cannot tell the result is true or false.
-let grantNotify = Near.view("social.near", "is_write_permission_granted", {
- predecessor_id: "${REPL_DEVHUB_CONTRACT}",
- key: context.accountId + "/index/notify",
-});
-if (grantNotify === null) {
- return;
-}
-
-const onSubmit = () => {
- let body = {
- name: state.name,
- description: generateDescription(
- state.description,
- state.amount,
- state.token,
- state.supervisor,
- state.seekingFunding
- ),
- };
-
- if (state.postType === "Solution") {
- body = {
- ...body,
- post_type: "Solution",
- solution_version: "V1",
- };
- } else {
- // Idea
- body = {
- ...body,
- post_type: "Idea",
- idea_version: "V1",
- };
- }
-
- let txn = [];
-
- txn.push({
- contractName: "${REPL_DEVHUB_CONTRACT}",
- methodName: "add_post",
- args: {
- parent_id: null,
- labels: state.labels,
- body: body,
- },
- gas: Big(10).pow(14),
- });
-
- if (grantNotify === false) {
- txn.unshift({
- contractName: "social.near",
- methodName: "grant_write_permission",
- args: {
- predecessor_id: "${REPL_DEVHUB_CONTRACT}",
- keys: [context.accountId + "/index/notify"],
- },
- gas: Big(10).pow(14),
- deposit: Big(10).pow(22),
- });
- }
-
- Near.call(txn);
-};
-
-const onIdeaClick = () => {
- State.update({ postType: "Idea", seekingFunding: false });
-};
-
-const onSolutionClick = () => {
- State.update({ postType: "Solution" });
-};
-
-const normalizeLabel = (label) =>
- label
- .replaceAll(/[- \.]/g, "_")
- .replaceAll(/[^\w]+/g, "")
- .replaceAll(/_+/g, "-")
- .replace(/^-+/, "")
- .replace(/-+$/, "")
- .toLowerCase()
- .trim("-");
-
-const checkLabel = (label) => {
- Near.asyncView("${REPL_DEVHUB_CONTRACT}", "is_allowed_to_use_labels", {
- editor: context.accountId,
- labels: [label],
- }).then((allowed) => {
- if (allowed) {
- State.update({ warning: "" });
- } else {
- State.update({
- warning:
- 'The label "' +
- label +
- '" is protected and can only be added by moderators',
- });
- return;
- }
- });
-};
-
-const setLabels = (labels) => {
- const normalizedLabels = labels.map((o) =>
- o.customOption ? normalizeLabel(o.label) : normalizeLabel(o)
- );
- const uniqueLabels = [...new Set(normalizedLabels)];
-
- if (uniqueLabels.length < state.labels.length) {
- const removedLabel = state.labels.find(
- (label) => !uniqueLabels.includes(label)
- );
-
- const allowed = Near.asyncView(
- "${REPL_DEVHUB_CONTRACT}",
- "is_allowed_to_use_labels",
- {
- editor: context.accountId,
- labels: [removedLabel],
- }
- );
-
- if (allowed) {
- State.update({ labels: uniqueLabels });
- } else {
- State.update({
- warning: `The label "${removedLabel}" is protected and can only be updated by moderators`,
- });
- }
- } else {
- State.update({ labels: uniqueLabels });
- }
-};
-
-const existingLabels =
- Near.view("${REPL_DEVHUB_CONTRACT}", "get_all_allowed_labels", {
- editor: context.accountId,
- }) ?? [];
-const allowedLabels = existingLabels.filter((it) => it !== "blog"); // remove blog label so users cannot publish blogs from feed
-
-function NameEditor() {
- return (
-
-
- Title
-
- State.update({ name: event.target.value })}
- />
-
- );
-}
-
-function DescriptionEditor() {
- return (
-
-
- Description
-
-
{
- State.update({ description: content, handler: "update" });
- textareaInputHandler(content);
- },
- }}
- />
- {autocompleteEnabled && state.showAccountAutocomplete && (
-
- State.update({ showAccountAutocomplete: false }),
- }}
- />
-
- )}
-
- );
-}
-
-function LabelsEditor() {
- return (
-
-
- Labels
-
- {
- return (
- !allowedLabels.includes(props.text) &&
- props.text.toLowerCase() !== "blog" && // dont allow adding "Blog"
- props.selected.filter((selected) => selected.name === props.text)
- .length == 0 &&
- Near.view("${REPL_DEVHUB_CONTRACT}", "is_allowed_to_use_labels", {
- editor: context.accountId,
- labels: [props.text],
- })
- );
- }}
- />
-
- );
-}
-
-function FundraisingToggle() {
- return (
- <>
-
-
- Are you seeking funding for your solution?
- (Optional)
-
-
-
- State.update({ seekingFunding: true })}
- />
- Yes
-
-
-
-
- State.update({ seekingFunding: false })}
- />
- No
-
-
-
- >
- );
-}
-
-function Fundraising() {
- return (
-
-
- Currency
- State.update({ token: event.target.value })}
- class="form-select"
- aria-label="Default select"
- >
-
- USDT
-
- NEAR
- USDC
-
-
-
- Requested amount{" "}
- (Numbers Only)
- 0 ? state.amount : ""}
- min={0}
- onChange={(event) =>
- State.update({
- amount: Number(
- event.target.value.toString().replace(/e/g, "")
- ).toString(),
- })
- }
- />
-
-
-
- Requested sponsor (Optional)
-
-
- If you are requesting funding from a specific sponsor, please enter
- their username.
-
-
-
- @
-
-
- State.update({ supervisor: event.target.value })
- }
- />
-
-
-
- );
-}
-
-function generateDescription(text, amount, token, supervisor, seekingFunding) {
- const fundingText =
- amount > 0 && token ? `###### Requested amount: ${amount} ${token}\n` : "";
- const supervisorText = supervisor
- ? `###### Requested sponsor: @${supervisor}\n`
- : "";
- return seekingFunding ? `${fundingText}${supervisorText}${text}` : text;
-}
-
-const [tab, setTab] = useState("editor");
-
-return (
-
-
- {props.transactionHashes ? (
- <>
- Post created successfully. Back to{" "}
-
- feed
-
- >
- ) : (
- <>
-
-
-
-
-
- setTab("editor")}
- >
- Editor
-
-
-
- setTab("preview")}
- >
- Preview
-
-
-
-
-
-
- {tab === "editor" && (
- <>
-
-
- What do you want to create?
-
-
- {Object.values(postTypeOptions).map((option) => (
- typeSwitch(option.name)}
- style={
- state.postType === option.name
- ? {
- backgroundColor: "#0C7283",
- color: "#f3f3f3",
- }
- : null
- }
- type="button"
- >
-
- {option.name}
-
- ))}
-
-
- {postTypeOptions[state.postType].description}
-
- {state.warning && (
-
- {state.warning}
- State.update({ warning: "" })}
- >
-
- )}
-
-
-
-
- {state.postType === "Solution" && }
- {state.seekingFunding && }
-
-
- Submit
-
-
- >
- )}
- {tab === "preview" && (
-
-
Preview
-
-
-
-
- Submit
-
-
- )}
-
-
- >
- )}
-
-
-);
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 000000000..367a268ba
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "skipLibCheck": true,
+ "baseUrl": "./src/",
+ "paths": { "@/includes/*": ["./includes/*"] },
+ "jsx": "preserve",
+ "types": ["@types/react", "near-social-vm-types", "./global.d.ts"],
+ "strict": true,
+ "noImplicitAny": true
+ }
+}