Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(PE-7142): set logo #598

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions jest.config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"testEnvironment": "jsdom",
"testTimeout": 60000,
"setupFilesAfterEnv": ["<rootDir>/jest-setup.ts"],
"collectCoverage": false,
"collectCoverageFrom": ["src/**/*.{ts,tsx,js,jsx}", "!src/**/*.d.ts"],
Expand Down
28 changes: 28 additions & 0 deletions src/components/forms/DomainSettings/DomainSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import ControllersRow from './ControllersRow';
import DomainSettingsRow from './DomainSettingsRow';
import IOCompatibleRow from './IOCompatibleRow';
import LogoRow from './LogoRow';
import NicknameRow from './NicknameRow';
import OwnerRow from './OwnerRow';
import TTLRow from './TTLRow';
Expand All @@ -54,16 +55,19 @@
OWNER = 'Owner',
TTL = 'TTL Seconds',
UNDERNAMES = 'Undernames',
LOGO_TX_ID = 'Logo TX ID',
}

function DomainSettings({
domain,
antId,
rowFilter = [],
setLogo,
}: {
domain?: string;
antId?: string;
rowFilter?: DomainSettingsRowTypes[];
setLogo?: (id?: string) => void;
}) {
const queryClient = useQueryClient();

Expand Down Expand Up @@ -96,8 +100,13 @@
if (!domain && !antId) {
navigate('/manage/names');
}
}, [domain, antId]);

Check warning on line 103 in src/components/forms/DomainSettings/DomainSettings.tsx

View workflow job for this annotation

GitHub Actions / lint_test_build

React Hook useEffect has a missing dependency: 'navigate'. Either include it or remove the dependency array

Check warning on line 103 in src/components/forms/DomainSettings/DomainSettings.tsx

View workflow job for this annotation

GitHub Actions / build_and_test / lint_test_build

React Hook useEffect has a missing dependency: 'navigate'. Either include it or remove the dependency array

// callback to set logo
useEffect(() => {
if (setLogo && data?.logo) setLogo(data.logo);
}, [data?.logo, setLogo]);

useEffect(() => {
if (interactionResult) {
queryClient.invalidateQueries({
Expand Down Expand Up @@ -126,7 +135,7 @@

refetch();
}
}, [interactionResult]);

Check warning on line 138 in src/components/forms/DomainSettings/DomainSettings.tsx

View workflow job for this annotation

GitHub Actions / lint_test_build

React Hook useEffect has missing dependencies: 'data?.processId', 'domain', 'queryClient', and 'refetch'. Either include them or remove the dependency array

Check warning on line 138 in src/components/forms/DomainSettings/DomainSettings.tsx

View workflow job for this annotation

GitHub Actions / build_and_test / lint_test_build

React Hook useEffect has missing dependencies: 'data?.processId', 'domain', 'queryClient', and 'refetch'. Either include them or remove the dependency array

function getStatus(endTimestamp?: number) {
if (!endTimestamp) {
Expand Down Expand Up @@ -404,6 +413,25 @@
}
/>
),
[DomainSettingsRowTypes.LOGO_TX_ID]: (
<LogoRow
key={DomainSettingsRowTypes.LOGO_TX_ID}
logoTxId={data?.logo}
editable={isAuthorized}
confirm={(logo: string) =>
dispatchANTInteraction({
payload: {
logo,
},
workflowName: ANT_INTERACTION_TYPES.SET_LOGO,
signer: wallet!.contractSigner!,
owner: walletAddress!.toString(),
processId: data?.processId,
dispatch,
})
}
/>
),
}).map(([rowName, row]) =>
rowFilter.includes(rowName as DomainSettingsRowTypes) ? <></> : row,
)}
Expand Down
129 changes: 129 additions & 0 deletions src/components/forms/DomainSettings/LogoRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import ValidationInput from '@src/components/inputs/text/ValidationInput/ValidationInput';
import ArweaveID, {
ArweaveIdTypes,
} from '@src/components/layout/ArweaveID/ArweaveID';
import ConfirmTransactionModal from '@src/components/modals/ConfirmTransactionModal/ConfirmTransactionModal';
import { ArweaveTransactionID } from '@src/services/arweave/ArweaveTransactionID';
import { useGlobalState } from '@src/state/contexts/GlobalState';
import {
ANT_INTERACTION_TYPES,
ContractInteraction,
VALIDATION_INPUT_TYPES,
} from '@src/types';
import { isArweaveTransactionID } from '@src/utils';
import { ARNS_TX_ID_ENTRY_REGEX } from '@src/utils/constants';
import eventEmitter from '@src/utils/events';
import { Skeleton } from 'antd';
import { useEffect, useState } from 'react';

import DomainSettingsRow from './DomainSettingsRow';

export default function LogoRow({
logoTxId,
confirm,
editable = false,
}: {
logoTxId?: string;
confirm: (logo: string) => Promise<ContractInteraction>;
editable?: boolean;
}) {
const [editing, setEditing] = useState<boolean>(false);
const [newLogoTxId, setNewLogoTxId] = useState<string>(logoTxId ?? '');
const [{ arweaveDataProvider }] = useGlobalState();
const [showModal, setShowModal] = useState<boolean>(false);

useEffect(() => {
setNewLogoTxId(logoTxId ?? '');
}, [logoTxId]);

async function handleSave(transactionId: string) {
try {
await confirm(transactionId);
} catch (error) {
eventEmitter.emit('error', error);
} finally {
setEditing(false);
setNewLogoTxId(logoTxId ?? '');
kunstmusik marked this conversation as resolved.
Show resolved Hide resolved
setShowModal(false);
}
}
return (
<>
<DomainSettingsRow
label="Logo TX ID:"
editable={editable}
value={
typeof logoTxId == 'string' ? (
!editing && isArweaveTransactionID(logoTxId) ? (
<ArweaveID
id={new ArweaveTransactionID(logoTxId)}
shouldLink
type={ArweaveIdTypes.TRANSACTION}
/>
) : (
<ValidationInput
customPattern={ARNS_TX_ID_ENTRY_REGEX}
catchInvalidInput={true}
showValidationIcon={editing}
onPressEnter={() => setShowModal(true)}
inputClassName={'domain-settings-input'}
inputCustomStyle={
editing
? {
background: 'var(--card-bg)',
borderRadius: 'var(--corner-radius)',
border: '1px solid var(--text-faded)',
padding: '3px',
}
: {
border: 'none',
background: 'transparent',
}
}
disabled={!editing}
placeholder={editing ? `Enter a Logo ID` : logoTxId}
value={newLogoTxId}
setValue={(e) => setNewLogoTxId(e)}
validationPredicates={{
[VALIDATION_INPUT_TYPES.ARWEAVE_ID]: {
fn: (id: string) =>
arweaveDataProvider.validateArweaveId(id),
},
}}
maxCharLength={(str) => str.length <= 43}
/>
)
) : (
<Skeleton.Input active />
)
}
editing={editing}
setEditing={() => setEditing(true)}
onSave={() => setShowModal(true)}
onCancel={() => {
setEditing(false);
setNewLogoTxId(logoTxId ?? '');
}}
/>
{showModal && (
<ConfirmTransactionModal
cancel={() => setShowModal(false)}
confirm={() => handleSave(newLogoTxId)}
interactionType={ANT_INTERACTION_TYPES.SET_LOGO}
content={
<>
<span>
By completing this action, you are going to change the Logo TX
ID of this token to <br />
<span className="text-color-warning">
{`"${newLogoTxId}"`}.
</span>
</span>
<span>Are you sure you want to continue?</span>
</>
}
/>
)}
</>
);
}
73 changes: 60 additions & 13 deletions src/components/pages/ManageDomain/ManageDomain.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,62 @@ import { usePrimaryName } from '@src/hooks/usePrimaryName';
import { useGlobalState, useModalState } from '@src/state';
import { useTransactionState } from '@src/state/contexts/TransactionState';
import { Star } from 'lucide-react';
import { useEffect } from 'react';
import { ReactNode, useEffect, useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';

import { decodeDomainToASCII } from '../../../utils';
import { HamburgerOutlineIcon } from '../../icons';
import './styles.css';

function AntLogoIcon({
id,
className,
icon = (
<HamburgerOutlineIcon
width={'20px'}
height={'20px'}
fill="var(--text-white)"
/>
),
}: {
id?: string;
className?: string;
icon?: ReactNode;
}) {
const [{ gateway }] = useGlobalState();
const [validImage, setValidImage] = useState(true);
const logoRef = useRef<HTMLImageElement>(null);

useEffect(() => {
if (!logoRef.current || !id) return;

const img = logoRef.current;

const handleError = () => setValidImage(false);

img.addEventListener('error', handleError);

return () => {
img.removeEventListener('error', handleError);
};
}, [logoRef, id]);

if (!id) return <>{icon}</>;

return (
<>
<img
ref={logoRef}
className={className ?? 'w-[30px] rounded-full'}
src={`https://${gateway}/${id}`}
alt="ant-logo"
style={{ display: validImage ? 'block' : 'none' }}
/>
{!validImage && icon}
</>
);
}

function ManageDomain() {
const { name } = useParams();
const navigate = useNavigate();
Expand All @@ -20,18 +69,17 @@ function ManageDomain() {
const [, dispatchModalState] = useModalState();
const { data: primaryNameData } = usePrimaryName();

useEffect(() => {
// Reset transaction state on unmount - clears transaction success banner
return () => {
dispatchTransactionState({ type: 'reset' });
};
}, []);
const [logoId, setLogoId] = useState<string | undefined>();

useEffect(() => {
if (!name) {
navigate('/manage/names');
return;
}
// Reset transaction state on unmount - clears transaction success banner
kunstmusik marked this conversation as resolved.
Show resolved Hide resolved
return () => {
dispatchTransactionState({ type: 'reset' });
};
}, [name]);

return (
Expand Down Expand Up @@ -61,11 +109,7 @@ function ManageDomain() {
}}
>
<h2 className="flex white center" style={{ gap: '16px' }}>
<HamburgerOutlineIcon
width={'20px'}
height={'20px'}
fill="var(--text-white)"
/>
<AntLogoIcon id={logoId} />
{decodeDomainToASCII(name!)}
<Star
className={
Expand Down Expand Up @@ -114,7 +158,10 @@ function ManageDomain() {
{name == primaryNameData?.name ? 'Remove Primary' : 'Make Primary'}
</button>
</div>
<DomainSettings domain={name} />
<DomainSettings
domain={name}
setLogo={(id?: string) => setLogoId(id)}
/>
</div>
</>
);
Expand Down
3 changes: 3 additions & 0 deletions src/hooks/useDomainInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function useDomainInfo({
ticker: string;
owner: string;
controllers: string[];
logo: string;
undernameCount?: number;
sourceCodeTxId?: string;
apexRecord: {
Expand Down Expand Up @@ -68,6 +69,7 @@ export default function useDomainInfo({
ticker: string;
owner: string;
controllers: string[];
logo: string;
undernameCount: number;
sourceCodeTxId?: string;
apexRecord: {
Expand Down Expand Up @@ -138,6 +140,7 @@ export default function useDomainInfo({
ticker,
owner,
controllers,
logo: state.Logo ?? '',
kunstmusik marked this conversation as resolved.
Show resolved Hide resolved
undernameCount,
apexRecord,
sourceCodeTxId: (state as any)?.['Source-Code-TX-ID'],
Expand Down
6 changes: 6 additions & 0 deletions src/state/actions/dispatchANTInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ export default async function dispatchANTInteraction({
undername: lowerCaseDomain(payload.subDomain),
});
break;
case ANT_INTERACTION_TYPES.SET_LOGO:
dispatchSigningMessage('Setting Logo, please wait...');
result = await antProcess.setLogo({
txId: payload.logo,
});
break;
case ANT_INTERACTION_TYPES.APPROVE_PRIMARY_NAME:
dispatchSigningMessage(
'Approving Primary Name request, please wait...',
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export enum ANT_INTERACTION_TYPES {
EVOLVE = 'Upgrade ANT',
APPROVE_PRIMARY_NAME = 'Approve Primary Name',
REMOVE_PRIMARY_NAMES = 'Remove Primary Names',
SET_LOGO = 'Set Logo',
}

export enum ARNS_INTERACTION_TYPES {
Expand Down
Loading