mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 19:30:38 +08:00
Add user identities to components
Signed-off-by: Sebastian Martinez <sebastinez@me.com>
This commit is contained in:
+8
-12
@@ -2,12 +2,11 @@
|
||||
import { onMount } from 'svelte';
|
||||
import { link } from 'svelte-routing';
|
||||
import { ethers } from 'ethers';
|
||||
import { safeLink, explorerLink, identifyAddress, formatAddress, AddressType } from '@app/utils';
|
||||
import { safeLink, explorerLink, identifyAddress, formatAddress, AddressType, parseEnsLabel } from '@app/utils';
|
||||
import { Profile } from '@app/profile';
|
||||
import Loading from '@app/Loading.svelte';
|
||||
import Avatar from "@app/Avatar.svelte";
|
||||
import type { Config } from '@app/config';
|
||||
import type { Registration } from '@app/base/registrations/registrar';
|
||||
import { getRegistration } from '@app/base/registrations/registrar';
|
||||
|
||||
export let address: string;
|
||||
export let config: Config;
|
||||
@@ -15,24 +14,21 @@
|
||||
export let noBadge = false;
|
||||
export let noAvatar = false;
|
||||
export let compact = false;
|
||||
// This property allows components eg. Header.svelte to pass a resolved profile object.
|
||||
export let profile: Profile | null = null;
|
||||
|
||||
let checksumAddress = compact
|
||||
? formatAddress(address)
|
||||
: ethers.utils.getAddress(address);
|
||||
let addressType: AddressType | null = null;
|
||||
let addressName: string | null = null;
|
||||
let info: Registration | null;
|
||||
|
||||
onMount(async () => {
|
||||
identifyAddress(address, config).then((t: AddressType) => addressType = t);
|
||||
if (resolve) {
|
||||
addressName = await config.provider.lookupAddress(address);
|
||||
if (addressName) {
|
||||
info = await getRegistration(addressName, config);
|
||||
}
|
||||
if (resolve && !profile) {
|
||||
Profile.get(address, config).then(p => profile = p);
|
||||
}
|
||||
});
|
||||
$: addressLabel = addressName ?? checksumAddress;
|
||||
$: addressLabel = profile?.name ? compact ? parseEnsLabel(profile.name, config) : profile.name : checksumAddress;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -59,7 +55,7 @@
|
||||
|
||||
<div class="address" title={address} class:no-badge={noBadge}>
|
||||
{#if !noAvatar}
|
||||
<Avatar inline source={info?.avatar ?? address} />
|
||||
<Avatar inline source={profile?.avatar ?? address} />
|
||||
{/if}
|
||||
{#if addressType === AddressType.Org}
|
||||
<a use:link href={`/orgs/${address}`}>{addressLabel}</a>
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { createIcon } from '@app/blockies';
|
||||
import { isAddress } from '@app/utils';
|
||||
import { isAddress, isRadicleId } from '@app/utils';
|
||||
|
||||
export let source: string;
|
||||
export let inline = false;
|
||||
@@ -10,7 +10,7 @@
|
||||
let container: HTMLElement;
|
||||
|
||||
onMount(() => {
|
||||
if (isAddress(source)) {
|
||||
if (isAddress(source) || isRadicleId(source)) {
|
||||
const seed = source.toLowerCase();
|
||||
const avatar = createIcon({
|
||||
seed,
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if isAddress(source)}
|
||||
{#if isAddress(source) || isRadicleId(source)}
|
||||
<div class="avatar" class:inline bind:this={container} class:glowOnHover title={source}/>
|
||||
{:else}
|
||||
<img class="avatar" class:inline src={source} class:glowOnHover alt="avatar"/>
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@
|
||||
value: string | null;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
resolve: boolean;
|
||||
editable: boolean;
|
||||
}
|
||||
</script>
|
||||
@@ -105,7 +106,7 @@
|
||||
<a class="link" href="{field.value}" target="_blank">{field.value}</a>
|
||||
</span>
|
||||
{:else if isAddress(field.value)}
|
||||
<Address address={field.value} {config} />
|
||||
<Address resolve={field.resolve} address={field.value} {config} />
|
||||
{:else}
|
||||
{field.value}
|
||||
{/if}
|
||||
|
||||
+20
-10
@@ -1,17 +1,18 @@
|
||||
<script lang="ts">
|
||||
// TODO: Shorten tx hash
|
||||
import { link } from "svelte-routing";
|
||||
import { formatBalance, formatAddress } from "@app/utils";
|
||||
import { formatAddress, formatBalance } from "@app/utils";
|
||||
import { error, Failure } from '@app/error';
|
||||
import { disconnectWallet } from "@app/session";
|
||||
import type { Session } from '@app/session';
|
||||
import Loading from '@app/Loading.svelte';
|
||||
import Logo from './Logo.svelte';
|
||||
import Connect from './Connect.svelte';
|
||||
import Logo from '@app/Logo.svelte';
|
||||
import Connect from '@app/Connect.svelte';
|
||||
import type { Config } from '@app/config';
|
||||
import { Profile } from "@app/profile";
|
||||
import Avatar from "./Avatar.svelte";
|
||||
|
||||
export let session: Session | null;
|
||||
export let config: Config | null;
|
||||
export let config: Config;
|
||||
|
||||
let sessionButton: HTMLElement | null = null;
|
||||
let sessionButtonHover = false;
|
||||
@@ -63,6 +64,11 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
.address {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 42px;
|
||||
margin-left: 2rem;
|
||||
width: 9.75rem;
|
||||
}
|
||||
@@ -148,11 +154,15 @@
|
||||
on:mouseover={() => sessionButtonHover = true}
|
||||
on:mouseout={() => sessionButtonHover = false}
|
||||
>
|
||||
{#if sessionButtonHover}
|
||||
Disconnect
|
||||
{:else}
|
||||
{formatAddress(address)}
|
||||
{/if}
|
||||
{#await Profile.get(address, config)}
|
||||
<Loading small center />
|
||||
{:then profile}
|
||||
{#if sessionButtonHover}
|
||||
Disconnect
|
||||
{:else}
|
||||
<Avatar source={profile.avatar ?? address} inline />{formatAddress(address)}
|
||||
{/if}
|
||||
{/await}
|
||||
</button>
|
||||
{:else if config}
|
||||
<span class="connect">
|
||||
|
||||
@@ -130,7 +130,7 @@
|
||||
<span><Address address={org.address} {config} /></span>
|
||||
|
||||
<span class="label">Owner</span>
|
||||
<span><Address address={org.owner} {config} /></span>
|
||||
<span><Address resolve address={org.owner} {config} /></span>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<Loading center />
|
||||
</div>
|
||||
{:then orgs}
|
||||
<List {orgs}>
|
||||
<List {config} {orgs}>
|
||||
<div class="orgs-empty">Orgs you are a member of show up here.</div>
|
||||
</List>
|
||||
{/await}
|
||||
@@ -81,7 +81,7 @@
|
||||
<Loading center />
|
||||
</div>
|
||||
{:then orgs}
|
||||
<List {orgs}>
|
||||
<List {config} {orgs}>
|
||||
<div class="orgs-empty">There are no orgs.</div>
|
||||
</List>
|
||||
{/await}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { Link } from 'svelte-routing';
|
||||
import type { Org } from '@app/base/orgs/Org';
|
||||
import Blockies from '@app/Blockies.svelte';
|
||||
import Avatar from '@app/Avatar.svelte';
|
||||
import { Profile } from '@app/profile';
|
||||
import type { Config } from '@app/config';
|
||||
import Loading from '@app/Loading.svelte';
|
||||
|
||||
export let config: Config;
|
||||
export let orgs: Org[];
|
||||
|
||||
const orgsAddresses = orgs.map(org => org.address);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -13,14 +19,30 @@
|
||||
margin: 3rem;
|
||||
display: inline-block;
|
||||
}
|
||||
.list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.loading {
|
||||
padding: 3rem 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#each orgs as org}
|
||||
<div class="org">
|
||||
<Link to={`/orgs/${org.address}`}>
|
||||
<Blockies glowOnHover address={org.address} />
|
||||
</Link>
|
||||
{#await Profile.getMulti(orgsAddresses, config)}
|
||||
<div class="loading">
|
||||
<Loading center />
|
||||
</div>
|
||||
{:else}
|
||||
<slot />
|
||||
{/each}
|
||||
{:then profiles}
|
||||
<div class="list">
|
||||
{#each profiles as profile}
|
||||
<div class="org">
|
||||
<Link to={`/orgs/${profile.address}`}>
|
||||
<Avatar glowOnHover source={profile.avatar ?? profile.address} />
|
||||
</Link>
|
||||
</div>
|
||||
{:else}
|
||||
<slot />
|
||||
{/each}
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
@@ -60,10 +60,6 @@ export class Org {
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
async lookupAddress(config: Config): Promise<string> {
|
||||
return await config.provider.lookupAddress(this.address);
|
||||
}
|
||||
|
||||
async setName(name: string, config: Config): Promise<TransactionResponse> {
|
||||
assert(config.signer);
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
import { onMount, createEventDispatcher } from 'svelte';
|
||||
import Modal from '@app/Modal.svelte';
|
||||
import type { Config } from '@app/config';
|
||||
import { formatAddress } from '@app/utils';
|
||||
import { formatAddress, isAddress } from "@app/utils";
|
||||
import Loading from '@app/Loading.svelte';
|
||||
import { assert } from '@app/error';
|
||||
import * as utils from '@app/utils';
|
||||
|
||||
import type { Org } from './Org';
|
||||
|
||||
@@ -38,7 +37,7 @@
|
||||
const onSubmit = async () => {
|
||||
assert(newOwner);
|
||||
|
||||
if (! utils.isAddress(newOwner)) {
|
||||
if (! isAddress(newOwner)) {
|
||||
state = State.Failed;
|
||||
error = `"${newOwner}" is not a valid Ethereum address.`;
|
||||
return;
|
||||
@@ -58,7 +57,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if state === State.Success}
|
||||
{#if state === State.Success && newOwner}
|
||||
<Modal floating small>
|
||||
<div slot="title">
|
||||
✅
|
||||
|
||||
+63
-83
@@ -1,40 +1,29 @@
|
||||
<script lang="ts">
|
||||
import * as ethers from 'ethers';
|
||||
import { onMount } from 'svelte';
|
||||
import type { SvelteComponent } from 'svelte';
|
||||
import Link from '@app/Link.svelte';
|
||||
import type { Config } from '@app/config';
|
||||
import type { Registration } from '@app/base/registrations/registrar';
|
||||
import { getRegistration } from '@app/base/registrations/registrar';
|
||||
import { parseEnsLabel, explorerLink } from '@app/utils';
|
||||
import { session } from '@app/session';
|
||||
import Loading from '@app/Loading.svelte';
|
||||
import Modal from '@app/Modal.svelte';
|
||||
import Error from '@app/Error.svelte';
|
||||
import Icon from '@app/Icon.svelte';
|
||||
import Blockies from '@app/Blockies.svelte';
|
||||
import SetName from '@app/ens/SetName.svelte';
|
||||
import Project from '@app/base/projects/Widget.svelte';
|
||||
import Address from '@app/Address.svelte';
|
||||
import Avatar from '@app/Avatar.svelte';
|
||||
import Message from '@app/Message.svelte';
|
||||
import * as utils from '@app/utils';
|
||||
|
||||
import { Org } from './Org';
|
||||
import TransferOwnership from './TransferOwnership.svelte';
|
||||
import { Org } from '@app/base/orgs/Org';
|
||||
import TransferOwnership from '@app/base/orgs/TransferOwnership.svelte';
|
||||
import { Profile } from '@app/profile';
|
||||
|
||||
export let address: string;
|
||||
export let config: Config;
|
||||
|
||||
let registration: Registration | null = null;
|
||||
let name: string | null = null;
|
||||
|
||||
const back = () => window.history.back();
|
||||
|
||||
onMount(async () => {
|
||||
name = await config.provider.lookupAddress(address);
|
||||
if (name) registration = await getRegistration(name, config);
|
||||
});
|
||||
|
||||
let setNameForm: typeof SvelteComponent | null = null;
|
||||
const setName = () => {
|
||||
setNameForm = SetName;
|
||||
@@ -44,8 +33,6 @@
|
||||
const transferOwnership = () => {
|
||||
transferOwnerForm = TransferOwnership;
|
||||
};
|
||||
|
||||
$: label = name && parseEnsLabel(name, config);
|
||||
$: isOwner = (org: Org): boolean => $session
|
||||
? utils.isAddressEqual(org.owner, $session.address)
|
||||
: false;
|
||||
@@ -97,11 +84,6 @@
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
.avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -119,6 +101,7 @@
|
||||
}
|
||||
.members {
|
||||
margin-top: 2rem;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.members .member {
|
||||
@@ -126,9 +109,6 @@
|
||||
align-items: center;
|
||||
margin-right: 1.5rem;
|
||||
}
|
||||
.members .member a {
|
||||
border-bottom: none;
|
||||
}
|
||||
.members .member-icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
@@ -141,33 +121,32 @@
|
||||
{:then org}
|
||||
{#if org}
|
||||
<main>
|
||||
{#await Profile.get(address, config)}
|
||||
<Loading fadeIn />
|
||||
{:then profile}
|
||||
<header>
|
||||
<div class="avatar">
|
||||
{#if registration && registration.avatar}
|
||||
<img src={registration.avatar} alt="avatar" />
|
||||
{:else}
|
||||
<Blockies address={org.address} />
|
||||
{/if}
|
||||
<Avatar source={profile.avatar ?? address} />
|
||||
</div>
|
||||
<div class="info">
|
||||
<span class="title bold">
|
||||
{registration ? label : ethers.utils.getAddress(address)}
|
||||
{parseEnsLabel(profile.name, config) ?? address}
|
||||
</span>
|
||||
<div class="links">
|
||||
{#if registration}
|
||||
{#if registration.url}
|
||||
<a class="url" href={registration.url}>{registration.url}</a>
|
||||
{/if}
|
||||
{#if registration.twitter}
|
||||
<a class="url" href={registration.twitter}>
|
||||
<Icon name="twitter" />
|
||||
</a>
|
||||
{/if}
|
||||
{#if registration.github}
|
||||
<a class="url" href={registration.github}>
|
||||
<Icon name="github" />
|
||||
</a>
|
||||
{/if}
|
||||
{#if profile.url}
|
||||
<a class="url" href={profile.url}>
|
||||
{profile.url}
|
||||
</a>
|
||||
{/if}
|
||||
{#if profile.twitter}
|
||||
<a class="url" href={profile.twitter}>
|
||||
<Icon name="twitter" />
|
||||
</a>
|
||||
{/if}
|
||||
{#if profile.github}
|
||||
<a class="url" href={profile.github}>
|
||||
<Icon name="github" />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -191,15 +170,11 @@
|
||||
<!-- Name -->
|
||||
<div class="label">Name</div>
|
||||
<div>
|
||||
{#await org.lookupAddress(config)}
|
||||
<div class="loading"><Loading small /></div>
|
||||
{:then name}
|
||||
{#if name}
|
||||
<Link to={`/registrations/${label}`}>{name}</Link>
|
||||
{:else}
|
||||
<span class="subtle">Not set</span>
|
||||
{/if}
|
||||
{/await}
|
||||
{#if profile.name}
|
||||
<Link to={`/registrations/${parseEnsLabel(profile.name, config)}`}>{profile.name}</Link>
|
||||
{:else}
|
||||
<span class="subtle">Not set</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
{#await isAuthorized(org)}
|
||||
@@ -214,38 +189,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="members">
|
||||
{#await org.getMembers(config)}
|
||||
<Loading center />
|
||||
{:then members}
|
||||
{#each members as address}
|
||||
<div class="member">
|
||||
<div class="member-icon">
|
||||
<Blockies {address} />
|
||||
</div>
|
||||
<a href={explorerLink(address, config)} target="_blank" class="member">
|
||||
{utils.formatAddress(address)}
|
||||
</a>
|
||||
{#await org.getMembers(config)}
|
||||
<Loading center />
|
||||
{:then members}
|
||||
{#if members.length > 0}
|
||||
<div class="members">
|
||||
{#each members as address}
|
||||
{#await Profile.get(address, config)}
|
||||
<Loading small center />
|
||||
{:then profile}
|
||||
<div class="member">
|
||||
<div class="member-icon">
|
||||
<Avatar source={profile.avatar ?? address} />
|
||||
</div>
|
||||
<Address {address} compact resolve noAvatar {config} />
|
||||
</div>
|
||||
{/await}
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{/await}
|
||||
</div>
|
||||
|
||||
<div class="projects">
|
||||
{#await org.getProjects(config)}
|
||||
<Loading center />
|
||||
{:then projects}
|
||||
{#each projects as project}
|
||||
<div class="project">
|
||||
<Project {project} org={org.address} {config} />
|
||||
</div>
|
||||
{/each}
|
||||
{:catch err}
|
||||
<Message error>
|
||||
<strong>Error: </strong> failed to load projects: {err.message}.
|
||||
</Message>
|
||||
{/await}
|
||||
</div>
|
||||
<div class="projects">
|
||||
{#await org.getProjects(config)}
|
||||
<Loading center />
|
||||
{:then projects}
|
||||
{#each projects as project}
|
||||
<div class="project">
|
||||
<Project {project} org={org.address} {config} />
|
||||
</div>
|
||||
{/each}
|
||||
{:catch err}
|
||||
<Message error>
|
||||
<strong>Error: </strong> failed to load projects: {err.message}.
|
||||
</Message>
|
||||
{/await}
|
||||
</div>
|
||||
{/await}
|
||||
</main>
|
||||
{:else}
|
||||
<Modal subtle>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import * as proj from '@app/project';
|
||||
import Loading from '@app/Loading.svelte';
|
||||
import Modal from '@app/Modal.svelte';
|
||||
import Blockies from '@app/Blockies.svelte';
|
||||
import Avatar from '@app/Avatar.svelte';
|
||||
|
||||
import Browser from './Browser.svelte';
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
<span class="maintainers">
|
||||
{#each project.meta.maintainers as user}
|
||||
<span class="maintainer">
|
||||
<Blockies address={user} />
|
||||
<Avatar source={user} />
|
||||
</span>
|
||||
{/each}
|
||||
</span>
|
||||
|
||||
@@ -45,19 +45,19 @@
|
||||
if (r) {
|
||||
fields = [
|
||||
{ name: "owner", placeholder: "",
|
||||
value: r.owner, editable: false },
|
||||
value: r.owner, resolve: true, editable: false },
|
||||
{ name: "address", placeholder: "Not set",
|
||||
value: r.address, editable: true },
|
||||
value: r.address, resolve: false, editable: true },
|
||||
{ name: "seed", placeholder: "Not set",
|
||||
value: r.seed, editable: true },
|
||||
value: r.seed, resolve: false, editable: true },
|
||||
{ name: "url", label: "URL", placeholder: "Not set",
|
||||
value: r.url, editable: true },
|
||||
value: r.url, resolve: false, editable: true },
|
||||
{ name: "avatar", placeholder: "Not set",
|
||||
value: r.avatar, editable: true },
|
||||
value: r.avatar, resolve: false, editable: true },
|
||||
{ name: "twitter", placeholder: "Not set",
|
||||
value: r.twitter, editable: true },
|
||||
value: r.twitter, resolve: false, editable: true },
|
||||
{ name: "github", placeholder: "Not set",
|
||||
value: r.github, editable: true },
|
||||
value: r.github, resolve: false, editable: true },
|
||||
];
|
||||
state = { status: Status.Found, registration: r };
|
||||
} else {
|
||||
|
||||
@@ -15,21 +15,28 @@
|
||||
onMount(async () => {
|
||||
if (query) {
|
||||
if (ethers.utils.isAddress(query)) {
|
||||
// Go to org.
|
||||
navigate(`/orgs/${query}`, { replace: true });
|
||||
const addressType = query && await utils.identifyAddress(query, config);
|
||||
if (addressType === utils.AddressType.Org) {
|
||||
navigate(`/orgs/${query}`, { replace: true });
|
||||
} else if (addressType === utils.AddressType.EOA) {
|
||||
navigate(`/users/${query}`, { replace: true });
|
||||
}
|
||||
} else if (utils.isRadicleId(query)) {
|
||||
// Go to Radicle entity.
|
||||
alert("Radicle IDs are not yet supported");
|
||||
} else {
|
||||
let label = utils.parseEnsLabel(query, config);
|
||||
if (label.includes(".")) {
|
||||
if (label?.includes(".")) {
|
||||
error = true;
|
||||
} else {
|
||||
// Jump straight to org, if the ENS entry points to an org. Otherwise just go to the
|
||||
// registration.
|
||||
// Jump straight to org, if the ENS entry points to an org. Otherwise it checks if the
|
||||
// address type is an EOA and jumps to the user page else it just goes to the registration.
|
||||
const address = await utils.resolveLabel(label, config);
|
||||
if (address && await utils.identifyAddress(address, config) === utils.AddressType.Org) {
|
||||
const addressType = address && await utils.identifyAddress(address, config);
|
||||
if (addressType === utils.AddressType.Org) {
|
||||
navigate(`/orgs/${address}`, { replace: true });
|
||||
} else if (addressType === utils.AddressType.EOA) {
|
||||
navigate(`/users/${address}`, { replace: true });
|
||||
} else {
|
||||
navigate(`/registrations/${label}`, { replace: true });
|
||||
}
|
||||
|
||||
+19
-24
@@ -1,22 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import type { Config } from '@app/config';
|
||||
import type { Registration } from '@app/base/registrations/registrar';
|
||||
import { getRegistration } from '@app/base/registrations/registrar';
|
||||
import Icon from '@app/Icon.svelte';
|
||||
import Address from '@app/Address.svelte';
|
||||
import Avatar from '@app/Avatar.svelte';
|
||||
import { Profile } from '@app/profile';
|
||||
import Loading from '@app/Loading.svelte';
|
||||
|
||||
export let address: string;
|
||||
export let config: Config;
|
||||
|
||||
let addressName: string | null = null;
|
||||
let info: Registration | null;
|
||||
|
||||
onMount(async () => {
|
||||
addressName = await config.provider.lookupAddress(address);
|
||||
info = await getRegistration(addressName, config);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -56,24 +47,27 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<main>
|
||||
<header>
|
||||
<div class="avatar">
|
||||
<Avatar source={info?.avatar ?? address} />
|
||||
</div>
|
||||
<div class="info">
|
||||
<span class="title bold"><Address noAvatar {address} {config} resolve/></span>
|
||||
{#await Profile.get(address, config)}
|
||||
<Loading fadeIn />
|
||||
{:then profile}
|
||||
<main>
|
||||
<header>
|
||||
<div class="avatar">
|
||||
<Avatar source={profile.avatar ?? address} />
|
||||
</div>
|
||||
<div class="info">
|
||||
<span class="title bold"><Address compact noAvatar {address} {config} resolve/></span>
|
||||
<div class="links">
|
||||
{#if info?.url}
|
||||
<a class="url" href={info.url}>{info.url}</a>
|
||||
{#if profile.url}
|
||||
<a class="url" href={profile.url}>{profile.url}</a>
|
||||
{/if}
|
||||
{#if info?.twitter}
|
||||
<a class="url" href={info.twitter}>
|
||||
{#if profile.twitter}
|
||||
<a class="url" href={profile.twitter}>
|
||||
<Icon name="twitter" />
|
||||
</a>
|
||||
{/if}
|
||||
{#if info?.github}
|
||||
<a class="url" href={info.github}>
|
||||
{#if profile.github}
|
||||
<a class="url" href={profile.github}>
|
||||
<Icon name="github" />
|
||||
</a>
|
||||
{/if}
|
||||
@@ -81,3 +75,4 @@
|
||||
</div>
|
||||
</header>
|
||||
</main>
|
||||
{/await}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { formatAddress } from '@app/utils';
|
||||
import { State, state } from './state';
|
||||
import { getInfo, withdrawVested } from './vesting';
|
||||
import type { VestingInfo } from './vesting';
|
||||
import type { Session } from '@app/session';
|
||||
import type { Config } from '@app/config';
|
||||
import Modal from '@app/Modal.svelte';
|
||||
import Address from '@app/Address.svelte';
|
||||
import { formatAddress } from '@app/utils';
|
||||
|
||||
let input: HTMLElement;
|
||||
|
||||
@@ -64,7 +65,7 @@
|
||||
Tokens successfully withdrawn to {formatAddress(info.beneficiary)}.
|
||||
{:else}
|
||||
<table>
|
||||
<tr><td class="label">Beneficiary</td><td>{info.beneficiary}</td></tr>
|
||||
<tr><td class="label">Beneficiary</td><td><Address {config} address={info.beneficiary} compact resolve /></td></tr>
|
||||
<tr><td class="label">Allocation</td><td>{info.totalVesting} <strong>{info.symbol}</strong></td></tr>
|
||||
<tr><td class="label">Withdrawn</td><td>{info.withdrawn} <strong>{info.symbol}</strong></td></tr>
|
||||
<tr><td class="label">Withdrawable</td><td>{info.withdrawableBalance} <strong>{info.symbol}</strong></td></tr>
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
},
|
||||
"ceramic": { "api": "https://ceramic-clay.3boxlabs.com" },
|
||||
"radicle": { "api": "" },
|
||||
"ipfs": { "gateway": "https://ipfs.io/ipfs/" },
|
||||
"abi": {
|
||||
"registrar": [
|
||||
"function rad() view returns (address)",
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import type { Registration } from "@app/base/registrations/registrar";
|
||||
import type { BasicProfile } from "@ceramicstudio/idx-constants";
|
||||
import { formatCAIP10Address, formatIpfsFile, resolveEnsProfile, resolveIdxProfile } from "@app/utils";
|
||||
import type { Config } from "./config";
|
||||
|
||||
export interface IProfile {
|
||||
ens: Registration | null;
|
||||
idx: BasicProfile | null;
|
||||
}
|
||||
|
||||
export class Profile {
|
||||
profile: IProfile;
|
||||
address: string;
|
||||
|
||||
constructor(profile: IProfile, address: string) {
|
||||
this.profile = profile;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
// Using undefined as return type if nothing to be returned since it works better with <a href> links
|
||||
get github(): string | undefined {
|
||||
if (this.profile?.ens?.github) return this.profile.ens.github;
|
||||
else if (this.profile?.idx?.affiliations) return this.profile.idx?.affiliations.find(item => item === "github");
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
// Using undefined as return type if nothing to be returned since it works better with <a href> links
|
||||
get twitter(): string | undefined {
|
||||
if (this.profile?.ens?.twitter) return this.profile.ens.twitter;
|
||||
else if (this.profile?.idx?.affiliations) return this.profile.idx.affiliations.find(item => item === "twitter");
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
// Using undefined as return type if nothing to be returned since it works better with <a href> links
|
||||
get url(): string | undefined {
|
||||
if (this.profile?.ens?.url) return this.profile.ens.url;
|
||||
else if (this.profile?.idx?.url) return this.profile.idx.url;
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
// Using undefined as return type if nothing to be returned since it works better with <a href> links
|
||||
get name(): string | undefined {
|
||||
if (this.profile?.ens?.name) return this.profile.ens.name;
|
||||
else if (this.profile?.idx?.name) return this.profile.idx.name;
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
// Using undefined as return type if nothing to be returned since it works better with <a href> links
|
||||
get avatar(): string | undefined {
|
||||
if (this.profile?.ens?.avatar) return this.profile.ens.avatar;
|
||||
else if (this.profile?.idx?.image?.original?.src) return formatIpfsFile(this.profile.idx.image.original.src);
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
// Keeping this function private since the desired entrypoint is .get()
|
||||
private static async lookupAddress(address: string, config: Config): Promise<[IProfile, string]> {
|
||||
const profile: IProfile = { ens: null, idx: null };
|
||||
|
||||
try {
|
||||
const [ens, idx] = await Promise.allSettled([
|
||||
resolveEnsProfile(address, config),
|
||||
resolveIdxProfile(formatCAIP10Address(address, "eip155", config.network.chainId), config)
|
||||
]);
|
||||
|
||||
if (ens.status == "fulfilled") profile.ens = ens.value;
|
||||
if (idx.status == "fulfilled") profile.idx = idx.value;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return [profile, address];
|
||||
}
|
||||
|
||||
static async getMulti(addresses: string[], config: Config): Promise<Profile[]> {
|
||||
const profilePromises = addresses.map(address => this.lookupAddress(address, config));
|
||||
const profiles = await Promise.all(profilePromises);
|
||||
return profiles.map(profile => { return new Profile(...profile); });
|
||||
}
|
||||
|
||||
static async get(
|
||||
address: string,
|
||||
config: Config,
|
||||
): Promise<Profile> {
|
||||
const profile = await this.lookupAddress(address, config);
|
||||
return new Profile(...profile);
|
||||
}
|
||||
}
|
||||
+22
-28
@@ -4,15 +4,12 @@ import multibase from 'multibase';
|
||||
import multihashes from 'multihashes';
|
||||
import EthersSafe from "@gnosis.pm/safe-core-sdk";
|
||||
import type { Config } from '@app/config';
|
||||
import config from "@app/config.json";
|
||||
import { assert } from '@app/error';
|
||||
import type { Registration } from "@app/base/registrations/registrar";
|
||||
import { getRegistration } from '@app/base/registrations/registrar';
|
||||
import type { BasicProfile } from "@ceramicstudio/idx-constants";
|
||||
|
||||
export interface Profile {
|
||||
ens: Registration | null;
|
||||
idx: BasicProfile | null;
|
||||
}
|
||||
|
||||
export enum AddressType {
|
||||
Contract,
|
||||
@@ -50,6 +47,11 @@ export function formatAddress(addr: string): string {
|
||||
return formatHash(ethers.utils.getAddress(addr));
|
||||
}
|
||||
|
||||
export function formatIpfsFile(ipfs: string | undefined): string | undefined {
|
||||
if (ipfs) return `${config.ipfs.gateway}${ipfs.replace("ipfs://", "")}`;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function formatHash(hash: string): string {
|
||||
return hash.substring(0, 6)
|
||||
+ '...'
|
||||
@@ -62,12 +64,14 @@ export function capitalize(s: string): string {
|
||||
}
|
||||
|
||||
// Takes a domain name, eg. 'cloudhead.radicle.eth' and
|
||||
// returns the label, eg. 'cloudhead', otherwise `null`.
|
||||
export function parseEnsLabel(name: string, config: Config): string {
|
||||
const domain = config.registrar.domain.replace(".", "\\.");
|
||||
const label = name.replace(new RegExp(`\\.${domain}$`), "");
|
||||
// returns the label, eg. 'cloudhead', otherwise `undefined`.
|
||||
export function parseEnsLabel(name: string | undefined, config: Config): string | undefined {
|
||||
if (name) {
|
||||
const domain = config.registrar.domain.replace(".", "\\.");
|
||||
const label = name.replace(new RegExp(`\\.${domain}$`), "");
|
||||
|
||||
return label;
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the current unix time.
|
||||
@@ -90,6 +94,12 @@ export function isDid(input: string): boolean {
|
||||
return /^did:[a-zA-Z0-9]+:[a-zA-Z0-9]+$/.test(input);
|
||||
}
|
||||
|
||||
export function isENSName(input: string, config: Config): boolean {
|
||||
const domain = config.registrar.domain.replace(".", "\\.");
|
||||
const regEx = new RegExp(`^[a-zA-Z0-9]+.${domain}$`);
|
||||
return regEx.test(input);
|
||||
}
|
||||
|
||||
// Check whether the input is an Ethereum address.
|
||||
export function isAddress(input: string): boolean {
|
||||
return ethers.utils.isAddress(input);
|
||||
@@ -195,27 +205,11 @@ export async function identifyAddress(address: string, config: Config): Promise<
|
||||
}
|
||||
|
||||
// Resolve a label under the radicle domain.
|
||||
export async function resolveLabel(label: string, config: Config): Promise<string | null> {
|
||||
return config.provider.resolveName(`${label}.${config.registrar.domain}`);
|
||||
export async function resolveLabel(label: string | undefined, config: Config): Promise<string | null> {
|
||||
if (label) return config.provider.resolveName(`${label}.${config.registrar.domain}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function lookupAddress(address: string, config: Config): Promise<Profile> {
|
||||
const profile: Profile = { ens: null, idx: null };
|
||||
|
||||
try {
|
||||
const [ens, idx] = await Promise.allSettled([
|
||||
resolveEnsProfile(address, config),
|
||||
resolveIdxProfile(formatCAIP10Address(address, "eip155", config.network.chainId), config)
|
||||
]);
|
||||
|
||||
if (ens.status == "fulfilled") profile.ens = ens.value;
|
||||
if (idx.status == "fulfilled") profile.idx = idx.value;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
// Resolves an IDX profile or return null
|
||||
export async function resolveIdxProfile(caip10: string, config: Config): Promise<BasicProfile | null> {
|
||||
|
||||
Reference in New Issue
Block a user