Radish alpha
r
Radicle web interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Scaffolding for orgs
Alexis Sellier committed 5 years ago
commit 8819c8c6f771b1dd78c0ca7204ecd77d84541f3e
parent 1f73869c399d03a10a4b536ecc18f5d347c776b1
10 files changed +227 -25
modified public/index.css
@@ -13,6 +13,7 @@
	--color-primary-6: #ffd4ff;
	--color-primary-faded: #ff55ff77;
	--color-secondary: #5555ff;
+
	--color-secondary-faded: #5555ff77;
	--color-secondary-1: #212847;
	--color-secondary-2: #2c326d;
	--color-secondary-6: #e3e3ff;
@@ -91,7 +92,7 @@ button {
	cursor: pointer;
	min-width: 8rem;
}
-
button:hover {
+
button:not([disabled]):hover {
	color: var(--color-background) !important;
	background-color: var(--color-foreground);
}
@@ -102,7 +103,11 @@ button.secondary {
	color: var(--color-secondary);
	border-color: var(--color-secondary);
}
-
button.secondary:hover {
+
button.secondary[disabled] {
+
	color: var(--color-secondary-faded);
+
	border-color: var(--color-secondary-faded);
+
}
+
button.secondary:not([disabled]):hover {
	background-color: var(--color-secondary);
}
button.primary {
@@ -175,7 +180,7 @@ input.wide {
	background: var(--color-background);
	box-shadow: 8px 8px 64px var(--box-shadow-color);
	min-width: 480px;
-
	max-width: 720px;
+
	max-width: 760px;
	text-align: center;
}
.modal-title {
@@ -185,6 +190,8 @@ input.wide {
	text-align: center;
}
.modal-body {
+
	overflow-x: hidden;
+
	text-overflow: ellipsis;
	margin-top: 2em;
	margin-bottom: 3em;
}
@@ -192,12 +199,22 @@ input.wide {
	margin-top: 2rem;
	text-align: center;
}
-
.modal-actions > button {
+
.modal-actions button {
	margin-right: 1rem;
}
-
.modal-actions > button:last-child {
+
.modal-actions button:last-child {
	margin-right: 0;
}
+
.modal.error .modal-title {
+
	color: var(--color-negative);
+
}
+
.modal.error .modal-actions button {
+
	color: var(--color-negative);
+
	border-color: var(--color-negative);
+
}
+
.modal.error .modal-actions button:hover {
+
	background-color: var(--color-negative);
+
}

.error {
	color: var(--color-negative);
@@ -214,12 +231,12 @@ button.error:hover {

table {
	table-layout: fixed;
-
	border-collapse: collapse;
-
	width: 100%;
-
	max-width: 480px;
+
	border-collapse: separate;
+
	border-spacing: 2rem 0;
}
td {
	text-align: left;
+
	text-overflow: ellipsis;
}
td.label {
	color: var(--color-secondary);
modified src/App.svelte
@@ -8,6 +8,7 @@

  import Vesting from '@app/base/vesting/Vesting.svelte';
  import Register from '@app/base/register/Routes.svelte';
+
  import Orgs from '@app/base/orgs/Routes.svelte';
  import Header from '@app/Header.svelte';

  const defaultPath = "register";
@@ -44,6 +45,7 @@
          <Vesting {config} />
        </Route>
        <Register {config} {query} />
+
        <Orgs {config} {query} />
      </Router>
    </div>
  </div>
modified src/Header.svelte
@@ -78,6 +78,7 @@
  <div>
    <span class="nav">
      <a use:link href="/register/">Register</a>
+
      <a use:link href="/orgs/">Orgs</a>
      <a use:link href="/vesting/">Vesting</a>
    </span>

modified src/Modal.svelte
@@ -1,7 +1,14 @@
<script lang="typescript">
+
  import { createEventDispatcher } from 'svelte';
+

  export let floating = false;
+
  export let error = null;
+

+
  let dispatch = createEventDispatcher();

-
  let className = floating ? "modal-floating" : "";
+
  const onClose = () => {
+
    dispatch('close');
+
  };
</script>

<style>
@@ -29,16 +36,34 @@
  <div class="modal-overlay"></div>
{/if}

-
<div class={className}>
-
  <div class="modal">
+
<div class:modal-floating={floating}>
+
  <div class="modal" class:error={error}>
    <div class="modal-title">
-
      <slot name="title"></slot>
+
      {#if error}
+
        Error
+
      {:else}
+
        <slot name="title"></slot>
+
      {/if}
    </div>
    <div class="modal-body">
-
      <slot name="body"></slot>
+
      {#if error}
+
        {#if error === Object(error) && error.message}
+
          <strong>Error:</strong> {error.message}
+
        {:else}
+
          {error}
+
        {/if}
+
      {:else}
+
        <slot name="body"></slot>
+
      {/if}
    </div>
    <div class="modal-actions">
-
      <slot name="actions"></slot>
+
      {#if error}
+
        <button on:click={onClose}>
+
          Dismiss
+
        </button>
+
      {:else}
+
        <slot name="actions"></slot>
+
      {/if}
    </div>
  </div>
</div>
added src/base/orgs/CreateOrg.svelte
@@ -0,0 +1,60 @@
+
<script lang="typescript">
+
  import { createEventDispatcher } from 'svelte';
+
  import { session } from '@app/session';
+
  import Modal from '@app/Modal.svelte';
+
  import { Org } from '@app/base/orgs/Org';
+

+
  export let config;
+
  export let owner;
+

+
  enum State {
+
    Idle,
+
    Waiting,
+
    Success,
+
  }
+

+
  let state = State.Idle;
+
  let error = null;
+

+
  const dispatch = createEventDispatcher();
+
  const createOrg = async () => {
+
    state = State.Waiting;
+

+
    console.log("creating org");
+
    try {
+
      let tx = await Org.create(owner, config);
+
      let receipt = await tx.wait();
+
      console.log(receipt);
+
      let org = Org.fromReceipt(receipt);
+
      console.log(org);
+
      state = State.Success;
+
    } catch (e) {
+
      state = State.Idle;
+
      console.error(e);
+
      error = e;
+
    }
+
  };
+
</script>
+

+
<Modal floating {error} on:close>
+
  <span slot="title">
+
    Create an Org
+
  </span>
+
  <span slot="body">
+
    <table>
+
      <tr><td class="label">Owner</td><td>{owner}</td></tr>
+
    </table>
+
  </span>
+
  <span slot="actions">
+
    <button
+
      on:click={createOrg}
+
      class="primary"
+
      data-waiting={state === State.Waiting || null}
+
      disabled={state !== State.Idle}
+
    >Create</button>
+

+
    <button on:click={() => dispatch('close')} class="text">
+
      Cancel
+
    </button>
+
  </span>
+
</Modal>
added src/base/orgs/Org.ts
@@ -0,0 +1,42 @@
+
import * as ethers from 'ethers';
+
import type { TransactionReceipt, TransactionResponse } from '@ethersproject/providers';
+
import type { ContractReceipt } from '@ethersproject/contracts';
+

+
import type { Config } from '@app/config';
+

+
const orgFactoryAbi = [
+
  "function createOrg(address[], uint256) returns (address)",
+
  "event OrgCreated(address, address)",
+
];
+

+
const orgAbi = ["function owner() view returns (address)"];
+

+
export class Org {
+
  address: string
+
  safe: string
+

+
  constructor(address: string, safe: string) {
+
    this.address = address;
+
    this.safe = safe;
+
  }
+

+
  static fromReceipt(receipt: ContractReceipt): Org {
+
    let event = receipt.events.find(e => e.event === 'OrgCreated');
+
    let address = event.args[0];
+
    let safe = event.args[1];
+

+
    return new Org(address, safe);
+
  }
+

+
  static async create(
+
    owner: string,
+
    config: Config,
+
  ): Promise<TransactionResponse> {
+
    const orgFactory = new ethers.Contract(
+
      config.orgFactory.address,
+
      orgFactoryAbi,
+
      config.signer
+
    );
+
    return await orgFactory.createOrg([owner], 1);
+
  }
+
}
added src/base/orgs/Orgs.svelte
@@ -0,0 +1,32 @@
+
<script lang="typescript">
+
  import { onMount } from 'svelte';
+
  import { get } from 'svelte/store';
+
  import { Router, Link, Route, navigate } from "svelte-routing";
+
  import { ethers } from 'ethers';
+
  import { error } from '@app/error';
+
  import { session } from '@app/session';
+
  import CreateOrg from '@app/base/orgs/CreateOrg.svelte';
+

+
  enum State {
+
    Idle,
+
  }
+

+
  export let config;
+
  export let query;
+

+
  let modal = null;
+
  let state = State.Idle;
+

+
  $: owner = $session.address;
+
</script>
+

+
<style>
+
</style>
+

+
<main>
+
  <button on:click={() => modal = CreateOrg} disabled={!owner} data-disabled-tooltip="No!" class="secondary">
+
    Create an Org
+
  </button>
+
</main>
+

+
<svelte:component this="{modal}" {owner} {config} on:close={() => modal = null} />
added src/base/orgs/Routes.svelte
@@ -0,0 +1,11 @@
+
<script lang="typescript">
+
  import { Route } from "svelte-routing";
+
  import Orgs from '@app/base/orgs/Orgs.svelte';
+

+
  export let config;
+
  export let query;
+
</script>
+

+
<Route path="/orgs">
+
  <Orgs {config} />
+
</Route>
modified src/base/register/steps/Submit.svelte
@@ -35,16 +35,12 @@
</script>

<style>
-
  .available {
-
    line-height: 1.75em;
-
    padding: 2rem;
-
  }
  .domain {
    color: var(--color-secondary);
  }
</style>

-
<div class="modal {error ? 'error' : ''}">
+
<div class="modal" class:error={error}>
  {#if error}
    <div class="modal-title error">
      Transaction failed
modified src/config.ts
@@ -6,14 +6,25 @@ declare global {
  }
}

-
const config = {
+
export type Config = {
+
  registrar: { address: string },
+
  radToken: { address: string },
+
  orgFactory: { address: string },
+
  provider: ethers.providers.JsonRpcProvider,
+
  signer: ethers.Signer,
+
};
+

+
const addresses = {
  homestead: {
    registrar: {
      address: "0x37723287Ae6F34866d82EE623401f92Ec9013154",
    },
    radToken: {
      address: "0x31c8EAcBFFdD875c74b94b077895Bd78CF1E64A3",
-
    }
+
    },
+
    orgFactory: {
+
      address: "0x0000000000000000000000000000000000000000",
+
    },
  },
  ropsten: {
    registrar: {
@@ -21,27 +32,32 @@ const config = {
    },
    radToken: {
      address: "0x59b5eee36f5fa52400A136Fd4630Ee2bF126a4C0",
-
    }
+
    },
+
    orgFactory: {
+
      address: "0xe30aA5594FFB52B6bF5bbB21eB7e71Ac525bB028",
+
    },
  }
};

-
function isMetamaskInstalled() {
+
function isMetamaskInstalled(): boolean {
  const { ethereum } = window;
  return Boolean(ethereum && ethereum.isMetaMask);
}

-
export async function getConfig() {
+
export async function getConfig(): Promise<Config> {
  if (isMetamaskInstalled()) {
    const provider = new ethers.providers.Web3Provider(window.ethereum);

    let network = await provider.ready;
-
    let cfg = config[network.name];
+
    let cfg = addresses[network.name];

    if (cfg) {
      return {
        registrar: cfg.registrar,
        radToken: cfg.radToken,
+
        orgFactory: cfg.orgFactory,
        provider: provider,
+
        signer: provider.getSigner(),
      };
    } else {
      throw `Wrong network: ${network.name}`;