From 8aa0b436d215367cd89b751f0c715a0f01a14332 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Nov 2024 13:53:48 +0200 Subject: [PATCH] Treasury self creation flow (#102) --- instances/treasury-factory.near/.DS_Store | Bin 0 -> 6148 bytes instances/treasury-factory.near/.gitignore | 2 + .../aliases.mainnet.json | 12 + .../treasury-factory.near/bos.config.json | 6 + instances/treasury-factory.near/data.json | 3 + instances/treasury-factory.near/src | 1 + .../treasury-factory.near/widget/app.jsx | 29 ++ .../widget/components/Info.jsx | 32 ++ .../widget/components/Modal.jsx | 97 +++++ .../widget/components/NewAccountInput.jsx | 61 +++ .../create-treasury/AddMemberForm.jsx | 73 ++++ .../create-treasury/AddMembersStep.jsx | 180 +++++++++ .../create-treasury/ConfirmWalletStep.jsx | 111 ++++++ .../create-treasury/CreateAppAccountStep.jsx | 58 +++ .../CreateSputnikAccountStep.jsx | 84 ++++ .../components/create-treasury/Stepper.jsx | 38 ++ .../create-treasury/SummaryStep.jsx | 370 ++++++++++++++++++ .../widget/components/templates/AppLayout.jsx | 53 +++ .../widget/config/css.jsx | 84 ++++ .../widget/pages/treasury/Create.jsx | 147 +++++++ package.json | 4 + 21 files changed, 1445 insertions(+) create mode 100644 instances/treasury-factory.near/.DS_Store create mode 100644 instances/treasury-factory.near/.gitignore create mode 100644 instances/treasury-factory.near/aliases.mainnet.json create mode 100644 instances/treasury-factory.near/bos.config.json create mode 100644 instances/treasury-factory.near/data.json create mode 120000 instances/treasury-factory.near/src create mode 100644 instances/treasury-factory.near/widget/app.jsx create mode 100644 instances/treasury-factory.near/widget/components/Info.jsx create mode 100644 instances/treasury-factory.near/widget/components/Modal.jsx create mode 100644 instances/treasury-factory.near/widget/components/NewAccountInput.jsx create mode 100644 instances/treasury-factory.near/widget/components/create-treasury/AddMemberForm.jsx create mode 100644 instances/treasury-factory.near/widget/components/create-treasury/AddMembersStep.jsx create mode 100644 instances/treasury-factory.near/widget/components/create-treasury/ConfirmWalletStep.jsx create mode 100644 instances/treasury-factory.near/widget/components/create-treasury/CreateAppAccountStep.jsx create mode 100644 instances/treasury-factory.near/widget/components/create-treasury/CreateSputnikAccountStep.jsx create mode 100644 instances/treasury-factory.near/widget/components/create-treasury/Stepper.jsx create mode 100644 instances/treasury-factory.near/widget/components/create-treasury/SummaryStep.jsx create mode 100644 instances/treasury-factory.near/widget/components/templates/AppLayout.jsx create mode 100644 instances/treasury-factory.near/widget/config/css.jsx create mode 100644 instances/treasury-factory.near/widget/pages/treasury/Create.jsx diff --git a/instances/treasury-factory.near/.DS_Store b/instances/treasury-factory.near/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7942de025763641c19af64cac6da3c25bb0d302e GIT binary patch literal 6148 zcmeHKI|@QE5ZqM}!N$@uSMUZw^aNhOLJ>g}#83Mw&*jma{WSWt(?(`tGs$KrAyd44 zD+4|n-D7zg6`%rtpn!cJ3f!z~C(au!pc4=H5#Hiv@r+ zu?a*3ra=V;RkOv=pd(%~uO>EuK^M*DL-S_M4n_TToL@X$v<7md0#x8xfo?2ER{!Vl z5B>i$i7P5V1^!9_?adamDV~(Iwe>iwwFSO}Th1MBhPhKPcsT}oImW`u@z|3huh<;> WHL(eFI^s?T@@K$wp;3WfEARjm5EY~V literal 0 HcmV?d00001 diff --git a/instances/treasury-factory.near/.gitignore b/instances/treasury-factory.near/.gitignore new file mode 100644 index 00000000..a7e8e215 --- /dev/null +++ b/instances/treasury-factory.near/.gitignore @@ -0,0 +1,2 @@ +build +dist \ No newline at end of file diff --git a/instances/treasury-factory.near/aliases.mainnet.json b/instances/treasury-factory.near/aliases.mainnet.json new file mode 100644 index 00000000..6c2e460f --- /dev/null +++ b/instances/treasury-factory.near/aliases.mainnet.json @@ -0,0 +1,12 @@ +{ + "REPL_DEVHUB": "devhub.near", + "REPL_DEVHUB_CONTRACT": "devhub.near", + "REPL_BASE_DEPLOYMENT_ACCOUNT": "treasury-factory.near", + "REPL_DEVDAO_ACCOUNT": "treasury-devdao.near", + "REPL_SPUTNIK_FACTORY_ACCOUNT": "sputnik-dao.near", + "REPL_NEAR": "near", + "REPL_MOB": "mob.near", + "REPL_SOCIAL_CONTRACT": "social.near", + "REPL_RPC_URL": "https://rpc.mainnet.near.org", + "REPL_PIKESPEAK_KEY": "36f2b87a-7ee6-40d8-80b9-5e68e587a5b5" +} diff --git a/instances/treasury-factory.near/bos.config.json b/instances/treasury-factory.near/bos.config.json new file mode 100644 index 00000000..190ec150 --- /dev/null +++ b/instances/treasury-factory.near/bos.config.json @@ -0,0 +1,6 @@ +{ + "account": "treasury-factory.near", + "aliasPrefix": "REPL", + "aliasesContainsPrefix": true, + "aliases": ["./aliases.mainnet.json"] +} diff --git a/instances/treasury-factory.near/data.json b/instances/treasury-factory.near/data.json new file mode 100644 index 00000000..30372d9c --- /dev/null +++ b/instances/treasury-factory.near/data.json @@ -0,0 +1,3 @@ +{ + "treasury-factory.near": {} +} diff --git a/instances/treasury-factory.near/src b/instances/treasury-factory.near/src new file mode 120000 index 00000000..0301008c --- /dev/null +++ b/instances/treasury-factory.near/src @@ -0,0 +1 @@ +widget \ No newline at end of file diff --git a/instances/treasury-factory.near/widget/app.jsx b/instances/treasury-factory.near/widget/app.jsx new file mode 100644 index 00000000..a8e02db6 --- /dev/null +++ b/instances/treasury-factory.near/widget/app.jsx @@ -0,0 +1,29 @@ +/** + * This is the main entry point for the Treasury application. + * Page route gets passed in through params, along with all other page props. + */ + +const { page, ...passProps } = props; + +// Import our modules +const { AppLayout } = VM.require( + "${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.templates.AppLayout" +) || { AppLayout: () => <> }; +const { Theme } = VM.require( + `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/config.css` +) || { + Theme: () => <>, +}; + +const propsToSend = { ...passProps }; + +return ( + + + + + +); diff --git a/instances/treasury-factory.near/widget/components/Info.jsx b/instances/treasury-factory.near/widget/components/Info.jsx new file mode 100644 index 00000000..115d1ac7 --- /dev/null +++ b/instances/treasury-factory.near/widget/components/Info.jsx @@ -0,0 +1,32 @@ +const { type, text } = props; + +const typeMapper = { + info: { icon: "bi-info-circle", color: "#555555", bg: "#F4F4F4" }, + alert: { icon: "bi-exclamation-triangle", color: "#B17108", bg: "#FF9E001A" }, +}; + +const Containet = styled.div` + display: flex; + padding: 10px 15px; + gap: 10px; + border-radius: 15px; + align-items: center; + background: ${typeMapper[type].bg}; + color: ${typeMapper[type].color}; + + i { + font-size: 20px; + } + + small { + font-size: 12px; + line-height: 15px; + } +`; + +return ( + + + {text} + +); diff --git a/instances/treasury-factory.near/widget/components/Modal.jsx b/instances/treasury-factory.near/widget/components/Modal.jsx new file mode 100644 index 00000000..cbfa910e --- /dev/null +++ b/instances/treasury-factory.near/widget/components/Modal.jsx @@ -0,0 +1,97 @@ +const { onClose, isOpen, heading, content } = props; + +const Modal = styled.div` + display: ${({ hidden }) => (hidden ? "none" : "flex")}; + position: fixed; + inset: 0; + justify-content: center; + align-items: center; + opacity: 1; + z-index: 999; + + .black-btn { + background-color: #000 !important; + border: none; + color: white; + &:active { + color: white; + } + } + + @media screen and (max-width: 768px) { + h5 { + font-size: 16px !important; + } + } + + .btn { + font-size: 14px; + } + + .theme-btn { + background-color: var(--theme-color) !important; + color: white; + } +`; + +const ModalBackdrop = styled.div` + position: absolute; + inset: 0; + background-color: rgba(0, 0, 0, 0.5); + opacity: 0.4; +`; + +const ModalDialog = styled.div` + padding: 1.5em; + z-index: 999; + overflow-y: auto; + max-height: 85%; + margin-top: 5%; + width: 35%; + + @media screen and (max-width: 768px) { + margin: 2rem; + width: 100%; + } +`; + +const ModalHeader = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + h5 { + font-size: 24px; + font-weight: 600; + line-height: 29.05px; + letter-spacing: -0.02em; + text-align: left; + } +`; + +const ModalContent = styled.div` + flex: 1; + font-size: 14px; + margin-top: 4px; + margin-bottom: 4px; + overflow-y: auto; + max-height: 50%; + text-align: left !important; + @media screen and (max-width: 768px) { + font-size: 12px !important; + } +`; + +return ( + +); diff --git a/instances/treasury-factory.near/widget/components/NewAccountInput.jsx b/instances/treasury-factory.near/widget/components/NewAccountInput.jsx new file mode 100644 index 00000000..929c1fac --- /dev/null +++ b/instances/treasury-factory.near/widget/components/NewAccountInput.jsx @@ -0,0 +1,61 @@ +const { alertMsg, setAlertMsg, onChange, defaultValue, postfix } = props; + +const [value, setValue] = useState(defaultValue ?? ""); + +const checkAccountAvailable = async (accountId) => { + if (accountId.length === 0) return; + + asyncFetch(`${REPL_RPC_URL}`, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: "dontcare", + method: "query", + params: { + request_type: "view_account", + finality: "final", + account_id: `${accountId}${postfix}`, + }, + }), + }).then((resp) => { + if (!resp) return; + + const err = resp.body?.error?.cause; + let errMsg = null; + + if (!err) errMsg = `Account ${accountId}${postfix} already been taken`; + else if (err.name !== "UNKNOWN_ACCOUNT") errMsg = err?.info?.error_message; + + setAlertMsg(errMsg); + }); +}; + +return ( +
+ { + const v = e.target.value; + checkAccountAvailable(v); + setValue(v); + onChange(v); + }} + /> +
+ {postfix} +
+
+); diff --git a/instances/treasury-factory.near/widget/components/create-treasury/AddMemberForm.jsx b/instances/treasury-factory.near/widget/components/create-treasury/AddMemberForm.jsx new file mode 100644 index 00000000..3974ca9a --- /dev/null +++ b/instances/treasury-factory.near/widget/components/create-treasury/AddMemberForm.jsx @@ -0,0 +1,73 @@ +const { fields, setFields, onClose, onSubmit } = props; + +const FormFields = styled.div` + .rbt-token { + background: transparent; + color: inherit; + border: 1px solid #e2e6ec; + border-radius: 32px; + padding: 2px 4px; + } + .rbt-menu > .dropdown-item:hover { + text-decoration: none; + } +`; + +const PERMISSIONS = { + create: "Create", + edit: "Edit", + vote: "Vote", +}; + +const [open, setOpen] = useState(false); + +return ( + +
+ + { + setFields({ + ...fields, + accountId: value ?? fields.accountId, + }); + }, + maxWidth: "100%", + }} + /> +
+
+ + { + setFields({ + ...fields, + permissions: value ?? fields.permissions, + }); + }} + options={Object.values(PERMISSIONS)} + positionFixed + multiple + /> +
+
+
+ Close +
+
0 && fields.permissions?.length > 0 + ? "" + : "disabled" + }`} + onClick={onSubmit} + > + Submit +
+
+
+); diff --git a/instances/treasury-factory.near/widget/components/create-treasury/AddMembersStep.jsx b/instances/treasury-factory.near/widget/components/create-treasury/AddMembersStep.jsx new file mode 100644 index 00000000..50a29609 --- /dev/null +++ b/instances/treasury-factory.near/widget/components/create-treasury/AddMembersStep.jsx @@ -0,0 +1,180 @@ +const { formFields, setFormFields } = props; + +const Badge = styled.div` + border: 1px solid #e2e6ec; + border-radius: 32px; + padding: 4px 10px; +`; + +const Item = styled.div` + border-bottom: 1px solid #e2e6ec; + padding: 10px 0; + + &:last-child { + border: 0; + } +`; + +const ActionButtons = styled.div` + i { + font-size: 18px; + } +`; + +const PERMISSIONS = { + create: "Create", + edit: "Edit", + vote: "Vote", +}; + +const [members, setMembers] = useState( + formFields.members ?? [ + { + accountId: context.accountId, + permissions: [PERMISSIONS.create], + }, + ] +); + +const [showAddMemberModal, setShowAddMemberModal] = useState(false); +const [fields, setFields] = useState({}); + +useEffect(() => { + setFormFields({ + ...formFields, + members, + }); +}, [members]); + +const ListItem = ({ member, key }) => ( + +
+ +
+ +
+ {member.permissions.map((permission, i) => ( + {permission} + ))} +
+ + + {key > 0 && ( + <> + { + setFields({ + accountId: member.accountId, + permissions: member.permissions, + }); + setShowAddMemberModal(true); + }} + /> + + setMembers( + members.filter((m) => m.accountId !== member.accountId) + ) + } + /> + + )} + +
+); + +function onClose() { + setShowAddMemberModal(false); +} + +function onSubmit() { + let newMembers = [ + ...members, + { + accountId: fields.accountId, + permissions: fields.permissions, + }, + ]; + newMembers = newMembers.filter( + (el, i) => + newMembers.map((m) => m.accountId).lastIndexOf(el.accountId) === i + ); + setMembers(newMembers); + setFields({}); + onClose(); +} + +return ( + <> +
+ + ), + confirmLabel: "Confirm", + showCanvas: showAddMemberModal, + onClose, + }} + /> +

Add Members

+

+ Set up who can access the treasury and what they can do. You can also do + this later. +

+
+
+ +
Account
+
Permissions
+
Actions
+
+
+ {members.map((member, i) => ( + + ))} +
+
+ + +
+ + Back + + + Next + +
+ +); diff --git a/instances/treasury-factory.near/widget/components/create-treasury/ConfirmWalletStep.jsx b/instances/treasury-factory.near/widget/components/create-treasury/ConfirmWalletStep.jsx new file mode 100644 index 00000000..780e99a5 --- /dev/null +++ b/instances/treasury-factory.near/widget/components/create-treasury/ConfirmWalletStep.jsx @@ -0,0 +1,111 @@ +const { getNearBalances } = VM.require( + "${REPL_DEVDAO_ACCOUNT}/widget/lib.common" +); +if (!getNearBalances) return <>; + +const baseUrl = "https://api.pikespeak.ai"; +const REQUIRED_BALANCE = 12; + +let balance = getNearBalances(context.accountId); +balance = balance ? parseFloat(balance.availableParsed) : 0; + +const Section = styled.div` + ul { + margin: 0; + padding: 0; + + li { + list-style: none; + padding: 8px 0; + border-bottom: 1px solid #e2e6ec; + + &:last-child { + border: 0; + } + } + } +`; + +const SummaryListItem = ({ title, value, info }) => ( +
  • +
    + {title} + {info && ( + {info}} + > + + + )} +
    + {value} NEAR +
  • +); + +return ( + <> +
    +

    Confirm your wallet

    +

    + This is the account that will be used to pay for creating the treasury + and managing it at first. You'll need to have enough funds in this + wallet to cover the setup costs. +

    +
    +
    +

    Connected Wallet

    + + +
    +

    Estimated one-time costs:

    +
      + + + + + +
    +
    + {balance < REQUIRED_BALANCE && ( + + )} +
    + + Yes, use this wallet and continue + + +); diff --git a/instances/treasury-factory.near/widget/components/create-treasury/CreateAppAccountStep.jsx b/instances/treasury-factory.near/widget/components/create-treasury/CreateAppAccountStep.jsx new file mode 100644 index 00000000..8eb2face --- /dev/null +++ b/instances/treasury-factory.near/widget/components/create-treasury/CreateAppAccountStep.jsx @@ -0,0 +1,58 @@ +const { formFields, setFormFields } = props; + +const [alertMsg, setAlertMsg] = useState(null); + +return ( + <> +
    +

    Create Application Account

    +

    + Enter a name for your treasury application. This name will be used for + the application's URL and other management purposes, not the actual + account where the funds will be held. +

    +
    + + + setFormFields({ + ...formFields, + accountName: v, + }), + }} + /> + + {alertMsg && ( + + )} + +
    + + Back + + + Next + +
    + +); diff --git a/instances/treasury-factory.near/widget/components/create-treasury/CreateSputnikAccountStep.jsx b/instances/treasury-factory.near/widget/components/create-treasury/CreateSputnikAccountStep.jsx new file mode 100644 index 00000000..1a212c7b --- /dev/null +++ b/instances/treasury-factory.near/widget/components/create-treasury/CreateSputnikAccountStep.jsx @@ -0,0 +1,84 @@ +const { formFields, setFormFields } = props; + +const LEARN_MORE_LINK = "https://github.com/near-daos/sputnik-dao-contract"; + +const [alertMsg, setAlertMsg] = useState(null); + +if (!formFields.sputnikAccountName) + setFormFields({ + ...formFields, + sputnikAccountName: formFields.accountName, + }); + +return ( + <> +
    +

    Create Sputnik DAO Account

    +

    + Enter the name for your treasury's Sputnik DAO account. This is where + the funds for your treasury will be held. +

    +
    + +
    + + setFormFields({ + ...formFields, + sputnikAccountName: v, + }), + }} + /> + + {alertMsg && ( + + )} + + + Sputnik DAO is a decentralized platform within the NEAR ecosystem + that allows communities to organize, manage governance, and make + collective decisions on-chain. + + Learn more + + + ), + }} + /> +
    + +
    + + Back + + + Next + +
    + +); diff --git a/instances/treasury-factory.near/widget/components/create-treasury/Stepper.jsx b/instances/treasury-factory.near/widget/components/create-treasury/Stepper.jsx new file mode 100644 index 00000000..f8a848f1 --- /dev/null +++ b/instances/treasury-factory.near/widget/components/create-treasury/Stepper.jsx @@ -0,0 +1,38 @@ +const { activeStep, steps } = props; + +const Container = styled.div` + display: flex; + flex-direction: row; + border-radius: 15px; + padding: 20px; + gap: 15px; + background: white; + border: 1px solid #e2e6ec; + + small { + line-height: 16px; + } +`; + +const Step = styled.div` + height: 4px; + border-radius: 4px; + background: #e2e6ec; + width: 100%; +`; + +return ( + <> +
    + {steps.map((_step, i) => ( + = i ? "bg-primary" : ""} + /> + ))} +
    + + {steps[parseInt(activeStep)]} + + +); diff --git a/instances/treasury-factory.near/widget/components/create-treasury/SummaryStep.jsx b/instances/treasury-factory.near/widget/components/create-treasury/SummaryStep.jsx new file mode 100644 index 00000000..a6c584ad --- /dev/null +++ b/instances/treasury-factory.near/widget/components/create-treasury/SummaryStep.jsx @@ -0,0 +1,370 @@ +const { formFields } = props; + +const REQUIRED_BALANCE = 12; + +const [showCongratsModal, setShowCongratsModal] = useState(false); + +const Section = styled.div` + display: flex; + flex-direction: column; + padding: 10px 0; + border-bottom: ${(props) => (props.withBorder ? "1px solid #E2E6EC" : "0")}; + + label { + font-size: 12px; + color: #999999; + margin-bottom: 5px; + } + + i { + font-size: 18px; + color: #060606; + } + + ul { + margin: 0; + padding: 0; + + li { + list-style: none; + padding: 8px 0; + border-bottom: 1px solid #e2e6ec; + + &:last-child { + border: 0; + } + } + } +`; + +const Badge = styled.div` + border: 1px solid #e2e6ec; + border-radius: 32px; + padding: 4px 10px; +`; + +const Item = styled.div` + border-bottom: 1px solid #e2e6ec; + padding: 10px 0; + + &:last-child { + border: 0; + } +`; + +const WidgetItemLink = styled.div` + border-bottom: 1px solid #e2e6ec; + padding: 10px 0; + + &:last-child { + border: 0; + } + + small { + font-size: 12px; + font-weight: 500; + line-height: 15px; + color: #b3b3b3; + } + + span { + font-size: 14px; + } + + a { + color: #060606; + } +`; + +const PERMISSIONS = { + create: "Create", + edit: "Edit", + vote: "Vote", +}; + +const storageAccountName = Storage.privateGet("accountName"); + +useEffect(() => { + if (storageAccountName) { + setShowCongratsModal(true); + Storage.privateSet("accountName", null); + } +}, [storageAccountName]); + +function filterMemberByPermission(permission) { + return formFields.members + .filter((acc) => acc.permissions.includes(permission)) + .map((acc) => acc.accountId); +} + +function createDao() { + const createDaoConfig = { + config: { + name: `${formFields.sputnikAccountName}`, + purpose: `creating ${formFields.sputnikAccountName} treasury`, + metadata: "", + }, + policy: { + roles: [ + { + kind: { + Group: filterMemberByPermission(PERMISSIONS.create), + }, + name: "Create Requests", + permissions: [ + "call:AddProposal", + "transfer:AddProposal", + "config:Finalize", + ], + vote_policy: {}, + }, + { + kind: { + Group: filterMemberByPermission(PERMISSIONS.edit), + }, + name: "Manage Members", + permissions: [ + "config:*", + "policy:*", + "add_member_to_role:*", + "remove_member_from_role:*", + ], + vote_policy: {}, + }, + { + kind: { + Group: filterMemberByPermission(PERMISSIONS.vote), + }, + name: "Vote", + permissions: ["*:VoteReject", "*:VoteApprove", "*:VoteRemove"], + vote_policy: {}, + }, + ], + default_vote_policy: { + weight_kind: "RoleWeight", + quorum: "0", + threshold: [1, 2], + }, + proposal_bond: "100000000000000000000000", + proposal_period: "604800000000000", + bounty_bond: "100000000000000000000000", + bounty_forgiveness_period: "604800000000000", + }, + }; + + Near.call([ + { + contractName: `${REPL_BASE_DEPLOYMENT_ACCOUNT}`, + methodName: "create_instance", + args: { + name: `${formFields.sputnikAccountName}`, + sputnik_dao_factory_account_id: `${REPL_SPUTNIK_FACTORY_ACCOUNT}`, + social_db_account_id: `${REPL_SOCIAL_CONTRACT}`, + widget_reference_account_id: `${formFields.accountName}.${REPL_NEAR}`, + create_dao_args: btoa(JSON.stringify(createDaoConfig)), + }, + gas: 300000000000000, + deposit: Big(REQUIRED_BALANCE).mul(Big(10).pow(24)).toFixed(), + }, + ]); + + Storage.privateSet("accountName", formFields.accountName); +} + +const CongratsItem = ({ title, link }) => ( + + {title} +
    + {link} +
    + clipboard.writeText(link)} + /> + + + +
    +
    +
    +); + +const SummaryListItem = ({ title, value, info }) => ( +
  • +
    + {title} + {info && ( + {info}} + > + + + )} +
    + {value} NEAR +
  • +); + +const ListItem = ({ member }) => ( + +
    + +
    + +
    + {member.permissions.map((permission, i) => ( + {permission} + ))} +
    +
    +); + +return ( + <> +
    +

    Summary

    + +
    +

    General

    +
    + + +
    +
    + +
    +
    +
    +
    + +
    + {formFields.accountName + ? `${formFields.accountName}.near` + : "-"} +
    +
    + + + +
    +
    + +
    +
    +
    + +
    + {formFields.sputnikAccountName + ? `${formFields.sputnikAccountName}.sputnik-dao.near` + : "-"} +
    +
    + + + +
    +
    +
    + +
    +
    +

    Members and permissions

    + + + +
    + {formFields.members && ( +
    +
    + {formFields.members.map((member, i) => ( + + ))} +
    +
    + )} +
    + +
    +

    Costs

    +
      + + + + + +
    +
    + + +
    + + {showCongratsModal && ( + +

    + You can access and manage your treasury using any of these + gateways. +

    +
    + + + +
    + + ), + onClose: () => setShowCongratsModal(false), + }} + /> + )} + +); diff --git a/instances/treasury-factory.near/widget/components/templates/AppLayout.jsx b/instances/treasury-factory.near/widget/components/templates/AppLayout.jsx new file mode 100644 index 00000000..a910b061 --- /dev/null +++ b/instances/treasury-factory.near/widget/components/templates/AppLayout.jsx @@ -0,0 +1,53 @@ +const data = fetch(`https://httpbin.org/headers`); +const gatewayURL = data?.body?.headers?.Origin ?? ""; + +// we need fixed positioning for near social and not for org +const ParentContainer = gatewayURL.includes("near.org") + ? styled.div` + width: 100%; + ` + : styled.div` + position: fixed; + inset: 73px 0px 0px; + width: 100%; + overflow-y: scroll; + background: var(--theme-bg-color) !important; + `; + +const Theme = styled.div` + display: flex; + flex-direction: column; + padding-top: calc(-1 * var(--body-top-padding)); + background: var(--theme-bg-color) !important; + + // remove up/down arrow in input of type = number + /* For Chrome, Safari, and Edge */ + input[type="number"]::-webkit-outer-spin-button, + input[type="number"]::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + /* For Firefox */ + input[type="number"] { + -moz-appearance: textfield; + } +`; + +const Container = styled.div` + width: 100%; +`; + +function AppLayout({ children }) { + return ( + + + +
    {children}
    +
    +
    +
    + ); +} + +return { AppLayout }; diff --git a/instances/treasury-factory.near/widget/config/css.jsx b/instances/treasury-factory.near/widget/config/css.jsx new file mode 100644 index 00000000..c14a5d87 --- /dev/null +++ b/instances/treasury-factory.near/widget/config/css.jsx @@ -0,0 +1,84 @@ +const Theme = styled.div` + --theme-color: rgba(44, 62, 80, 1); + --theme-bg-color: #f4f4f4; + --page-header-color: rgba(54, 61, 69, 1); + --text-color: white; + --link-inactive-color: white; + --link-active-color: white; + --border-color: rgba(226, 230, 236, 1); + --light-grey-color: rgba(185, 185, 185, 1); + --dark-grey-color: rgba(103, 103, 103, 1); + + .btn { + display: flex; + align-items: center; + justify-content: center; + height: 40px; + border-radius: 8px; + font-size: 15px; + font-weight: 500; + + &:active, + &:focus { + border: 0; + } + + &:hover { + text-decoration: none; + color: black !important; + font-weight: 700 !important; + } + } + + .btn-outline-plain { + border: 1px solid #e2e6ec; + background-color: transparent; + color: black !important; + + &:active, + &:focus { + border: 1px solid #e2e6ec; + } + } + + .btn-primary { + &:hover { + font-weight: 500 !important; + color: white !important; + } + } + + .nav a { + text-decoration: none; + color: var(--link-inactive-color) !important; + &.active { + color: var(--link-active-color) !important; + } + + &:hover { + color: var(--link-active-color) !important; + } + } + + .page-header { + color: var(--page-header-color); + } + + .text-light-grey { + color: var(--light-grey-color); + } + + .text-muted { + color: var(--dark-grey-color); + } + + .text-md { + font-size: 15px; + } + + .primary-text-color { + color: var(--theme-color); + } +`; + +return { Theme }; diff --git a/instances/treasury-factory.near/widget/pages/treasury/Create.jsx b/instances/treasury-factory.near/widget/pages/treasury/Create.jsx new file mode 100644 index 00000000..40010686 --- /dev/null +++ b/instances/treasury-factory.near/widget/pages/treasury/Create.jsx @@ -0,0 +1,147 @@ +const { isNearSocial } = VM.require( + "${REPL_DEVDAO_ACCOUNT}/widget/lib.common" +) || { + isNearSocial: false, +}; + +const { step } = props; + +const STATIC_IMAGES = { + social: + "https://ipfs.near.social/ipfs/bafkreicse7okbzvbsy2s6ykp7vj6sgwbkx4gnbsjlfeeepev3ams6ckbfa", + near: "https://ipfs.near.social/ipfs/bafkreihfzqpk2t3663foue7bgarjpnpp75pfohr2f7isgm4h6izieqi6ui", +}; +const widgetBasePath = `${REPL_BASE_DEPLOYMENT_ACCOUNT}/widget/components.create-treasury`; +const [formFields, setFormFields] = useState({}); + +const STEPS = [ + , + , + , + , + , +]; + +const Container = styled.div` + display: flex; + flex-direction: row; + border-radius: 15px; + padding: 20px; + gap: 15px; + background: white; + border: 1px solid #e2e6ec; + justify-content: center; + align-items: center; + color: #1b1b18; +`; + +const PageWrapper = styled.div` + width: 560px; + font-size: 14px; + + p { + font-size: 14px; + line-height: 18px; + margin-bottom: 0; + } + + input::placeholder { + color: #1b1b18; + opacity: 0.3; + } + + h3 { + font-size: 24px; + font-weight: 600; + } + + h4 { + font-size: 18px; + font-weight: 600; + } +`; + +const Wrapper = ({ title, children }) => ( +
    +
    +
    + +
    +
    +

    {title}

    +
    +
    + + + {children} + +
    +); + +const SocalSignIn = () => ( + +
    +

    + Click the 'Sign In' button at the top-right corner of the Near.Social + gateway +

    + +
    + +
    +); + +const NearSignIn = () => ( + +
    +

    + Click the 'Sign-up or Login' button located at the bottom-left corner of + the Near gateway +

    +
    + +
    +); + +const loading = ( + +); + +return ( + + {context.accountId ? ( + + ) : ( + <>{isNearSocial ? : } + )} + +); diff --git a/package.json b/package.json index 86ccecd3..18442836 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,10 @@ "prepare": "husky install", "bw:dev:instances": "bw ws dev", "bw:dev:devdao": "bw dev instances/treasury-devdao.near", + "bw:dev:treasury-factory": "bw dev instances/treasury-factory.near", "bw:build:instance": "npm run bw build instances/$npm_config_instance build/$npm_config_instance && mv build/$npm_config_instance/src/widget/* build/$npm_config_instance/src/ && rm -Rf build/$npm_config_instance/src/widget", "bw:build:devdao": "npm run bw:build:instance --instance=treasury-devdao.near", + "bw:build:treasury-factory": "npm run bw:build:instance --instance=treasury-factory.near", "bw:build:testing": "npm run bw:build:instance --instance=treasury-testing.near", "bw:build:infinex": "npm run bw:build:instance --instance=treasury-infinex.near", "bw:build:infinex-testing": "npm run bw:build:instance --instance=treasury-testing-infinex.near", @@ -38,6 +40,8 @@ "dry-run:instances": "cd ./build/$npm_config_instance && bos components diff $npm_config_instance network-config mainnet", "deploy:devdao": "npm run bw:build:devdao && cd ./build/treasury-devdao.near && bos components deploy", "dry-run:devdao": "npm run bw:build:devdao && npm run dry-run:instances --instance=treasury-devdao.near", + "deploy:treasury-factory": "npm run bw:build:treasury-factory && cd ./build/treasury-factory.near && bos components deploy", + "dry-run:treasury-factory": "npm run bw:build:treasury-factory && npm run dry-run:instances --instance=treasury-factory.near", "deploy:testing": "npm run bw:build:testing && cd ./build/treasury-testing.near && bos components deploy", "dry-run:testing": "npm run bw:build:testing && npm run dry-run:instances --instance=treasury-testing.near", "deploy:infinex": "npm run bw:build:infinex && cd ./build/treasury-infinex.near && bos components deploy",