Skip to content

Commit

Permalink
New delegation UI, #3945 (#3946)
Browse files Browse the repository at this point in the history
* Init delegation page, #3945

* Init delegate container, #3945

* Implement my delegation page, #3945 (#3951)

* Extract delegation layout

* Implement my delegation page, #3945

* Rename route, #3945

* Refactor democracy and open gov pallet tabs

* Refactor open gov delegation summary card, #3945

* Refactor summary cards, #3945

* Remove duplicated been delegated components, #3945

* Extract common column definition, #3945

* Refector my delegation, #3945 (#3956)

* Refactor common list columns, #3945

* fix: remove democracy delegation button style, #3945

* Add module switch for delegation, #3945

* Add delegate card and show basic data, #3945

* Add account links to delegate card, #3945

* Improve the way to query all been delegated, #3945 (#3962)

* Subscribe address track delegation, #3945 (#3963)

* Subscribe address democracy delegation, #3945 (#3965)

* Show participation rate, #3945

* Add title for delegates section, #3945

* Set my delegation received as the default tab, #3945

* Delegation stats, #3945 (#3966)

* Add referenda delegation statistics, #3945

* Add democracy delegation stats, #3945

* Refactor, #3945

* fix: display default 0, #3945

* Update tab and router name, #3945

* Improve title, #3945

* Move summary panel position, #3945 (#3970)

* Delegation track select, #3945 (#3973)

* Add track select to statistics, #3945

* Update list detail button, #3945

* Add divider to track select, #3945

* fix: layout, #3945

* fix lodash-es

* fix import

* fix: lodash es

* fix: import

* Account delegation tab, #3945 (#3974)

* fix: hide tab for kintsugi and collectives

* fix: mobile tab title no wrap

* fix: delegate card layout (#3978)

* feat: tooltip support delayduration

* fix: delegate card layout, add short description

* fix divider margin

* feat: show all my referenda delegation (#3980)

* showDetailButton prop

* feat: show all my referenda delegation

* fix: color

* refactor: all my delegation, keep code similar (#3982)

* feat: democracy all my delegation (#3981)

* feat: delegation card new delegate button (#3986)

* swap delegate popup form track and target

* target field support disabled and address props

* delegate popup support defaultTargetAddress and targetDisabled props

* new delegate button support defaultTargetAddress and targetDisabled props

* delegation card new delegate button

* feat: delegation init detail popup (#3989)

* feat: delegation, been delegated list (#3991)

* Add recent votes tab, #3984 (#3993)

* Add recent votes tab, #3984

* fix

* fix: ssr next api import, #3945 (#3994)

* fix: use tab module

* feat: delegation tab been delegated detail (#3995)

* fix: delegation card hide councilor link (#3999)

* Add delegatee detail announcement tab, #3996 (#3998)

* Add delegatee detail announcement tab, #3996

* Support parity announcement, #3996

* Add delegatee detail edit & revoke buttons, #3945 (#4002)

* Update empty announcement display, #3984 (#4003)

* feat: delegation democracy (#4001)

* init democracy delegation

* fix: new delegate button

* hide councilor link

* democracy

* fix: delegatee detail buttons (#4005)

* feat: delegation, democracy actions (#4008)

* add disabled prop

* if has delegating, then disabled the button

* detail popup actions

* fix: delegation, democracy, hide new delegate button (#4011)

* fix

* Add announcement edit popup, #4004 (#4007)

* Add announcement edit popup, #4004

* Edit delegatee announcement, #4004

* revert: delegation detail buttons (#4012)

* fix: delegation, my delegation api call (#4018)

* fix: recent vote list items count, typo and hide action buttons, #3945 (#4019)

* fix: rename tab been delegated -> received delegations

* fix: rename tab, been delegated -> received delegations

* Improve text, #3945

* Allow changing delegate target address from popup, #3945 (#4022)

* Allow changing delegate target address from popup, #3945

* fix: democracy delegation

* fix: delegation, detail link (#4023)

* Show prior to show delegates image avatar, #3945 (#4026)

* feat: delegation prompt (#4025)

* delegation prompt

* cookie key

* use prompt

* rename

* add assert

* move

* Add announcement publish coming soon, #3945 (#4030)

* Add announcement publish coming soon, #3945

* fix: mobile layout

* fix

* feat: delegation, add sort select (#4031)

* fix: moonriver moonbeam modules settings

* Replace add delegation button with a link to delegation page, #3945 (#4032)

* Replace add delegation button with a link to delegation page in referenda list page, #3945

* fix

* Update text

* Use lower case tab ID in query params, #3945

* Support remove all delegation in opengov mode on my delegation, #4020 (#4033)

* feat: delegation, search (#4034)

* urls

* move target field default address

* referenda

* democracy

* fix: input support classname

* refactor: move code

* fix referenda

* feat: delegation, address select add invalid message (#4037)

* fix: summary import

* Remove track filter event text, #3945 (#4042)

* Hide dropdown select scrollbar (#4043)

* fix: my delegation tab highlight, #3945 (#4044)

* Improve tab url to avoid redirect, #3945 (#4045)

* Improve tab url to avoid redirect, #3945

* fix

* fix: delegation menu active state, #3945 (#4046)

---------

Co-authored-by: hyifeng <[email protected]>
Co-authored-by: 2nthony <[email protected]>
  • Loading branch information
3 people authored Mar 21, 2024
1 parent f20899c commit 2de4b23
Show file tree
Hide file tree
Showing 178 changed files with 3,940 additions and 778 deletions.
2 changes: 1 addition & 1 deletion packages/kintsugi-next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"dependencies": {
"@interlay/monetary-js": "^0.5.3",
"@next/env": "^13.3.0",
"@osn/icons": "^1.74.0",
"@osn/icons": "^1.80.0",
"@svgr/webpack": "^7.0.0",
"bignumber.js": "^9.0.2",
"clsx": "^1.2.1",
Expand Down
9 changes: 9 additions & 0 deletions packages/next-common/components/addressCombo.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ export default function AddressCombo({
const { identity } = useChainSettings();
const [identities, setIdentities] = useState({});

const isValidAddress =
!allowInvalidAddress && (isAddress(address) || normalizeAddress(address));

const fetchAddressIdentity = useCallback(
(address) => {
const identityAddress = encodeAddressToChain(address, identity);
Expand Down Expand Up @@ -291,6 +294,12 @@ export default function AddressCombo({
)}
</Select>
{show && (accounts || []).length > 0 && listOptions}

{!isValidAddress && (
<div className="mt-2 text-red500 text12Medium">
Please fill a valid address
</div>
)}
</Wrapper>
);
}
20 changes: 18 additions & 2 deletions packages/next-common/components/avatar.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import makeBlockie from "ethereum-blockies-base64";
import { isEthereumAddress } from "@polkadot/util-crypto";
import { isPolkadotAddress } from "next-common/utils/viewfuncs";
import { tryConvertToEvmAddress } from "next-common/utils/hydradxUtil";
import { useAddressAvatarMap } from "next-common/context/avatar";

const StyledIdenticon = styled(Identicon)`
circle:first-child {
Expand Down Expand Up @@ -33,9 +34,24 @@ export default function Avatar({ address, size = 24 }) {

const maybeEvmAddress = tryConvertToEvmAddress(address);

if (isEthereumAddress(maybeEvmAddress)) {
const imgSize = (size / 10) * 8;
const addressAvatarMap = useAddressAvatarMap();
const image = addressAvatarMap?.get(maybeEvmAddress);
const imgSize = (size / 10) * 8;
if (image) {
return (
<Wrapper size={size}>
<ImgWrapper
size={imgSize}
src={image}
width={imgSize}
height={imgSize}
alt={maybeEvmAddress}
/>
</Wrapper>
);
}

if (isEthereumAddress(maybeEvmAddress)) {
return (
<Wrapper size={size}>
<ImgWrapper
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import Editor from "next-common/components/editor";
import InputText from "next-common/components/inputText";
import {
Referenda,
useModuleTab,
} from "next-common/components/profile/votingHistory/common";
import SignerPopup from "next-common/components/signerPopup";
import { useSignMessage } from "next-common/hooks/useSignMessage";
import nextApi from "next-common/services/nextApi";
import { setDemocracyDelegatesTriggerUpdate } from "next-common/store/reducers/democracy/delegates";
import { setReferendaDelegatesTriggerUpdate } from "next-common/store/reducers/referenda/delegates";
import {
newErrorToast,
newSuccessToast,
} from "next-common/store/reducers/toastSlice";
import { useCallback, useState } from "react";
import { useDispatch } from "react-redux";

export default function AnnouncementEditPopup({
title = "Publish",
onClose,
address,
}) {
const [announcement, setAnnouncement] = useState("");
const [announcementContentType, setAnnouncementContentType] =
useState("markdown");
const [shortBio, setShortBio] = useState("");
const signMessage = useSignMessage();
const dispatch = useDispatch();
const tab = useModuleTab();
const module = tab === Referenda ? "referenda" : "democracy";

const triggerUpdate = useCallback(() => {
if (module === "referenda") {
dispatch(setReferendaDelegatesTriggerUpdate());
} else if (module === "democracy") {
dispatch(setDemocracyDelegatesTriggerUpdate());
}
}, [module]);

const handleSubmit = useCallback(
async (api, signerAccount) => {
try {
const data = {
shortBio,
announcement,
announcementContentType,
};
data.signature = await signMessage(
JSON.stringify(data),
signerAccount.realAddress,
signerAccount.meta.source,
);
data.signerWallet = signerAccount.meta.source;

const { error } = await nextApi.post(
`delegation/${module}/delegates/${address}/announcement`,
data,
);
if (error) {
dispatch(newErrorToast(error.message));
return;
}
dispatch(newSuccessToast("Announcement published successfully"));
triggerUpdate();
onClose();
} catch (e) {
if (e.message === "Cancelled") {
return;
}
dispatch(newErrorToast(e.message));
}
},
[
module,
address,
shortBio,
announcement,
announcementContentType,
signMessage,
onClose,
triggerUpdate,
],
);

return (
<SignerPopup
className="w-[800px] max-w-full"
title={title}
onClose={onClose}
actionCallback={handleSubmit}
>
<div className="flex flex-col gap-[8px]">
<span className="text12Bold">Short Bio</span>
<InputText value={shortBio} setValue={setShortBio} />
</div>
<div className="flex flex-col gap-[8px]">
<span className="text12Bold">Announcement</span>
<Editor
value={announcement}
onChange={setAnnouncement}
contentType={announcementContentType}
setContentType={setAnnouncementContentType}
loadSuggestions={() => []}
minHeight={100}
previewerPlugins={[]}
setQuillRef={() => {}}
/>
</div>
</SignerPopup>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { cn } from "next-common/utils";
import { useNavCollapsed } from "next-common/context/nav";

export default function MemberCardListContainer({ children }) {
const [navCollapsed] = useNavCollapsed();

return (
<div
className={cn(
"grid gap-4",
"grid-cols-3",
navCollapsed
? "max-md:grid-cols-2"
: "max-md:grid-cols-1 max-lg:grid-cols-2",
"max-sm:grid-cols-1",
)}
>
{children}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import SecondaryButton from "next-common/lib/button/secondary";
import { SystemEdit2, SystemSubtract } from "@osn/icons/subsquare";
import { useState } from "react";
import AnnouncementEditPopup from "../AnnouncementEditPopup";
import useRealAddress from "next-common/utils/hooks/useRealAddress";

export function EditButton({ address }) {
const [showEdit, setShowEdit] = useState(false);
return (
<>
<SecondaryButton
size="small"
iconLeft={<SystemEdit2 className="w-4 h-4" />}
onClick={() => setShowEdit(true)}
>
Edit
</SecondaryButton>
{showEdit && (
<AnnouncementEditPopup
title="Edit"
onClose={() => setShowEdit(false)}
address={address}
/>
)}
</>
);
}

export function RevokeButton() {
return (
<SecondaryButton
size="small"
iconLeft={<SystemSubtract className="w-4 h-4" />}
>
Revoke
</SecondaryButton>
);
}

export default function DetailButtons({ address }) {
const realAddress = useRealAddress();
const isMyDelegate = address === realAddress;

return (
isMyDelegate && (
<div className="flex gap-[8px]">
<EditButton address={address} />
<RevokeButton />
</div>
)
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SystemMember } from "@osn/icons/subsquare";
import NoData from "next-common/components/noData";

export default function DelegateEmpty() {
return (
<NoData
icon={<SystemMember className="[&_path]:stroke-textTertiary" />}
text="No delegates"
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function InfoLine({ children }) {
return <div className="flex gap-x-4 mt-3">{children}</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function InfoTitle({ children }) {
return (
<h3 className="leading-4 text12Medium text-textTertiary mb-1">
{children}
</h3>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function InfoWrapper({ children }) {
return <div className="flex-1 grow leading-none">{children}</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { isNil } from "lodash-es";
import { SystemLoading } from "@osn/icons/subsquare";

export default function DelegatesLoadable({ delegates, children }) {
if (isNil(delegates)) {
return (
<SystemLoading className="my-6 [&_path]:stroke-textTertiary mx-auto" />
);
}

return children;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { MenuDelegation } from "@osn/icons/subsquare";
import { noop } from "lodash-es";
import Input from "next-common/components/input";

export default function DelegationSearchInput({
address = "",
setAddress = noop,
delegateButton,
}) {
return (
<div className="flex items-center gap-x-2">
<Input
className="w-full h-10 bg-neutral100"
placeholder="Please fill an address to delegate votes"
prefix={<MenuDelegation className="text-textTertiary" />}
value={address}
onChange={(e) => {
setAddress(e.target.value);
}}
/>

{delegateButton}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { noop } from "lodash-es";
import Select from "next-common/components/select";

export default function DelegationSortSelect({ sort = "", setSort = noop }) {
return (
<div className="flex items-center gap-x-2 text12Medium text-textSecondary">
Sort by
<Select
className="w-40 text12Medium"
value={sort}
onChange={(item) => {
setSort(item.value);
}}
options={[
{
label: <Label>Delegated Votes</Label>,
value: "votes",
},
{
label: <Label>Delegators</Label>,
value: "",
},
{
label: <Label>Participation</Label>,
value: "participation",
},
]}
small
/>
</div>
);
}

function Label({ children }) {
return <span className="text12Medium">{children}</span>;
}
27 changes: 27 additions & 0 deletions packages/next-common/components/delegation/delegate/container.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { TitleContainer } from "next-common/components/styled/containers/titleContainer";
import {
ModuleTab,
ModuleTabProvider,
} from "next-common/components/profile/votingHistory/common";
import useDelegationModuleTabs from "next-common/components/delegation/delegate/useModuleTabs";
import DelegatesSection from "next-common/components/delegation/delegate/delegatesSection";
import MyDelegationSection from "./myDelegationSection";

export default function DelegateContainer() {
const { moduleTabs, defaultModuleTab } = useDelegationModuleTabs();

return (
<ModuleTabProvider availableTabs={moduleTabs} defaultTab={defaultModuleTab}>
<div className="flex flex-col gap-y-4">
<TitleContainer>
<span>Delegates</span>
<ModuleTab />
</TitleContainer>

<MyDelegationSection />

<DelegatesSection />
</div>
</ModuleTabProvider>
);
}
Loading

0 comments on commit 2de4b23

Please sign in to comment.