Skip to content

Commit

Permalink
fix: make the updatemetadata modal great gain (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
chalabi2 authored Dec 5, 2024
1 parent 4f8b533 commit b7fdf05
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 37 deletions.
3 changes: 3 additions & 0 deletions components/bank/components/tokenList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default function TokenList({
const [selectedDenom, setSelectedDenom] = useState<any>(null);
const [isSendModalOpen, setIsSendModalOpen] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [openDenomInfoModal, setOpenDenomInfoModal] = useState(false);

const isMobile = useIsMobile();

Expand Down Expand Up @@ -223,6 +224,8 @@ export default function TokenList({
<DenomInfoModal
denom={filteredBalances.find(b => b.denom === selectedDenom)?.metadata ?? null}
modalId="denom-info-modal"
openDenomInfoModal={openDenomInfoModal}
setOpenDenomInfoModal={setOpenDenomInfoModal}
/>
<SendModal
modalId="send-modal"
Expand Down
10 changes: 7 additions & 3 deletions components/factory/components/DenomImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ProfileAvatar from '@/utils/identicon';
import Image from 'next/image';
import { useState, useEffect } from 'react';

const supportedDomains = [
export const supportedDomains = [
'imgur.com',
'i.imgur.com',
'cloudfront.net',
Expand All @@ -21,11 +21,13 @@ const supportedDomains = [
'giphy.com',
'dropboxusercontent.com',
'googleusercontent.com',
'upload.wikimedia.org',
'unsplash.com',
'istockphoto.com',
't4.ftcdn.net',
];

const supportedPatterns = [
export const supportedPatterns = [
/^https:\/\/.*\.s3\.amazonaws\.com/,
/^https:\/\/.*\.storage\.googleapis\.com/,
/^https:\/\/.*\.cloudinary\.com/,
Expand All @@ -37,6 +39,7 @@ const supportedPatterns = [
/^https:\/\/.*\.dropboxusercontent\.com/,
/^https:\/\/.*\.googleusercontent\.com/,
/^https:\/\/.*\.unsplash\.com/,
/^https:\/\/.*\.upload\.wikimedia\.org/,
/^https:\/\/.*\.istockphoto\.com/,
/^https:\/\/.*\.media\.giphy\.com/,
/^https:\/\/.*\.media\.istockphoto\.com/,
Expand All @@ -47,9 +50,10 @@ const supportedPatterns = [
/^https:\/\/.*\.twimg\.com/,
/^https:\/\/.*\.pinimg\.com/,
/^https:\/\/.*\.giphy\.com/,
/^https:\/\/.*\.t4\.ftcdn\.net/,
];

const isUrlSupported = (url: string) => {
export const isUrlSupported = (url: string) => {
try {
const { hostname } = new URL(url);
return (
Expand Down
3 changes: 2 additions & 1 deletion components/factory/components/MyDenoms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export default function MyDenoms({
};

const handleUpdateModalClose = () => {
setSelectedDenom(null);
setOpenUpdateDenomMetadataModal(false);
setModalType(null);
router.push('/factory', undefined, { shallow: true });
Expand Down Expand Up @@ -333,7 +334,7 @@ export default function MyDenoms({
<MintModal
admin={poaAdmin ?? 'manifest1afk9zr2hn2jsac63h4hm60vl9z3e5u69gndzf7c99cqge3vzwjzsfmy9qj'}
isPoaAdminLoading={isPoaAdminLoading}
denom={selectedDenom}
denom={modalType === 'mint' ? selectedDenom : null}
address={address}
refetch={refetchDenoms}
balance={selectedDenom?.balance ?? '0'}
Expand Down
1 change: 1 addition & 0 deletions components/factory/forms/BurnForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ export default function BurnForm({
isOpen={isContactsOpen}
setOpen={setIsContactsOpen}
showContacts={true}
currentAddress={address}
onSelect={(selectedAddress: string) => {
setRecipient(selectedAddress);
setFieldValue('recipient', selectedAddress);
Expand Down
5 changes: 3 additions & 2 deletions components/factory/forms/MintForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ export default function MintForm({
rightElement={
<button
type="button"
style={{ transition: 'none' }}
aria-label="contacts-btn"
onClick={() => setIsContactsOpen(true)}
className="btn btn-primary transition-none btn-sm text-white absolute right-2 top-1/2 -translate-y-1/2"
className="btn btn-primary btn-sm text-white"
>
<MdContacts className="w-5 h-5" />
</button>
Expand Down Expand Up @@ -210,6 +210,7 @@ export default function MintForm({
isOpen={isContactsOpen}
setOpen={setIsContactsOpen}
showContacts={true}
currentAddress={address}
onSelect={(selectedAddress: string) => {
setRecipient(selectedAddress);
setFieldValue('recipient', selectedAddress);
Expand Down
22 changes: 17 additions & 5 deletions components/factory/modals/MintModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,27 @@ export default function MintModal({
admin ?? 'manifest1afk9zr2hn2jsac63h4hm60vl9z3e5u69gndzf7c99cqge3vzwjzsfmy9qj'
);

const members = groupByAdmin?.groups?.[0]?.members;
const isAdmin = members?.some(member => member?.member?.address === address);
const isLoading = isPoaAdminLoading || isGroupByAdminLoading;
if (!isOpen) {
return null;
}

if (!denom || !address) {
console.warn('MintModal: Missing required props', { denom, address });
return null;
return (
<dialog className={`modal ${isOpen ? 'modal-open' : ''} z-[10000]`}>
<div className="modal-box max-w-6xl mx-auto rounded-[24px] bg-[#F4F4FF] dark:bg-[#1D192D] z-[10000] shadow-lg">
<div className="skeleton h-[17rem] max-h-72 w-full"></div>
</div>
<form method="dialog" className="modal-backdrop" onSubmit={onClose}>
<button>close</button>
</form>
</dialog>
);
}

const members = groupByAdmin?.groups?.[0]?.members;
const isAdmin = members?.some(member => member?.member?.address === address);
const isLoading = isPoaAdminLoading || isGroupByAdminLoading;

const safeBalance = balance || '0';
const safeTotalSupply = totalSupply || '0';

Expand Down
56 changes: 30 additions & 26 deletions components/factory/modals/updateDenomMetadata.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ import { truncateString, ExtendedMetadataSDKType } from '@/utils';
import { useEffect } from 'react';

const TokenDetailsSchema = Yup.object().shape({
display: Yup.string().required('Display is required').noProfanity(),
name: Yup.string().required('Name is required').noProfanity(),
display: Yup.string().noProfanity(),
name: Yup.string().noProfanity(),
description: Yup.string()
.required('Description is required')
.min(10, 'Description must be at least 10 characters long')
.noProfanity(),
uri: Yup.string().url('Must be a valid URL'),
uri: Yup.string()
.url('Must be a valid URL')
.matches(/^https:\/\//i, 'URL must use HTTPS protocol')
.matches(/\.(jpg|jpeg|png|gif)$/i, 'URL must point to an image file')
.supportedImageUrl(),
});

export function UpdateDenomMetadataModal({
Expand All @@ -34,10 +37,15 @@ export function UpdateDenomMetadataModal({
modalId: string;
onSuccess: () => void;
}) {
const handleCloseModal = (formikReset?: () => void) => {
setOpenUpdateDenomMetadataModal(false);
formikReset?.();
};

useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape' && openUpdateDenomMetadataModal) {
setOpenUpdateDenomMetadataModal(false);
handleCloseModal();
}
};

Expand Down Expand Up @@ -67,24 +75,25 @@ export function UpdateDenomMetadataModal({
const { estimateFee } = useFeeEstimation(chainName);
const { setDenomMetadata } = osmosis.tokenfactory.v1beta1.MessageComposer.withTypeUrl;

const handleUpdate = async (values: TokenFormData) => {
const handleUpdate = async (values: TokenFormData, resetForm: () => void) => {
setIsSigning(true);
const symbol = values.display.toUpperCase();
try {
const msg = setDenomMetadata({
sender: address,
metadata: {
description: values.description,
denomUnits: [
{ denom: fullDenom, exponent: 0, aliases: [symbol] },
{ denom: values.display, exponent: 6, aliases: [fullDenom] },
],
description: values.description || formData.description,
denomUnits:
[
{ denom: fullDenom, exponent: 0, aliases: [symbol] },
{ denom: symbol, exponent: 6, aliases: [fullDenom] },
] || formData.denomUnits,
base: fullDenom,
display: symbol,
name: values.name,
name: values.name || formData.name,
symbol: symbol,
uri: values.uri,
uriHash: '', // Leave this empty if you don't have a URI hash
uri: values.uri || formData.uri,
uriHash: '',
},
});

Expand All @@ -93,8 +102,7 @@ export function UpdateDenomMetadataModal({
fee,
onSuccess: () => {
onSuccess();
const modal = document.getElementById(modalId) as HTMLDialogElement;
modal?.close();
handleCloseModal(resetForm);
},
});
} catch (error) {
Expand All @@ -105,25 +113,21 @@ export function UpdateDenomMetadataModal({
};

return (
<dialog
id={modalId}
className={`modal ${openUpdateDenomMetadataModal ? 'modal-open' : ''}`}
onClose={() => setOpenUpdateDenomMetadataModal(false)}
>
<dialog id={modalId} className={`modal ${openUpdateDenomMetadataModal ? 'modal-open' : ''}`}>
<Formik
initialValues={formData}
validationSchema={TokenDetailsSchema}
onSubmit={handleUpdate}
onSubmit={(values, { resetForm }) => handleUpdate(values, resetForm)}
validateOnChange={true}
validateOnBlur={true}
>
{({ isValid, dirty, values, handleChange, handleSubmit }) => (
{({ isValid, dirty, values, handleChange, handleSubmit, resetForm }) => (
<div className="modal-box max-w-4xl mx-auto p-6 bg-[#F4F4FF] dark:bg-[#1D192D] rounded-[24px] shadow-lg">
<form method="dialog">
<button
type="button"
className="btn btn-sm btn-circle btn-ghost absolute right-4 top-4 text-[#00000099] dark:text-[#FFFFFF99] hover:bg-[#0000000A] dark:hover:bg-[#FFFFFF1A]"
onClick={() => setOpenUpdateDenomMetadataModal(false)}
onClick={() => handleCloseModal(() => resetForm())}
>
</button>
Expand Down Expand Up @@ -176,7 +180,7 @@ export function UpdateDenomMetadataModal({
<button
type="button"
className="btn w-1/2 focus:outline-none dark:bg-[#FFFFFF0F] bg-[#0000000A] dark:text-white text-black"
onClick={() => setOpenUpdateDenomMetadataModal(false)}
onClick={() => handleCloseModal(() => resetForm())}
>
Cancel
</button>
Expand All @@ -193,7 +197,7 @@ export function UpdateDenomMetadataModal({
)}
</Formik>
<form method="dialog" className="modal-backdrop">
<button onClick={() => setOpenUpdateDenomMetadataModal(false)}>close</button>
<button onClick={() => handleCloseModal()}>close</button>
</form>
</dialog>
);
Expand Down
12 changes: 12 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const nextConfig = {
output: 'standalone',
reactStrictMode: true,
swcMinify: true,
// TODO: Remove this when we are ready for prod
typescript: {
ignoreBuildErrors: true,
},
Expand All @@ -19,6 +20,9 @@ const nextConfig = {
'images.unsplash.com',
'media.giphy.com',
'media.istockphoto.com',
'upload.wikimedia.org',
'istockphoto.com',
't4.ftcdn.net',
],
remotePatterns: [
{
Expand Down Expand Up @@ -69,6 +73,14 @@ const nextConfig = {
protocol: 'https',
hostname: '*.istockphoto.com',
},
{
protocol: 'https',
hostname: '*.upload.wikimedia.org',
},
{
protocol: 'https',
hostname: '*.t4.ftcdn.net',
},
],
},
};
Expand Down
21 changes: 21 additions & 0 deletions styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,24 @@ input:-webkit-autofill:active {
[data-toast-container='true'] {
z-index: 100000;
}

.tooltip {
position: relative;
cursor: help;
}

.tooltip[data-tooltip]:hover::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 0;
background: #333;
color: white;
padding: 8px;
border-radius: 4px;
font-size: 14px;
white-space: pre-wrap;
width: max-content;
max-width: 300px;
z-index: 1000;
}
30 changes: 30 additions & 0 deletions utils/yupExtensions.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as Yup from 'yup';
import { containsProfanity } from '@/utils/profanityFilter';
import { bech32 } from '@scure/base'; // Updated import
import { supportedDomains, supportedPatterns } from '@/components/factory/components/DenomImage';

declare module 'yup' {
interface StringSchema {
noProfanity(message?: string): this;
manifestAddress(message?: string): this;
simulateDenomCreation(simulateFn: () => Promise<boolean>, message?: string): this;
simulateDenomMetadata(simulateFn: () => Promise<boolean>, message?: string): this;
supportedImageUrl(message?: string): this;
}
}

Expand Down Expand Up @@ -115,4 +117,32 @@ Yup.addMethod<Yup.StringSchema>(Yup.string, 'manifestAddress', function (message
});
});

Yup.addMethod<Yup.StringSchema>(Yup.string, 'supportedImageUrl', function (message) {
return this.test('supported-image-url', message, function (value) {
const { path, createError } = this;
if (!value) return true;

try {
const { hostname } = new URL(value);
const isSupported =
supportedDomains.includes(hostname) ||
supportedPatterns.some(pattern => pattern.test(value));

return (
isSupported ||
createError({
path,
message:
message || `URL domain is not supported. Please use one of the supported domains`,
})
);
} catch {
return createError({
path,
message: message || 'Invalid URL format',
});
}
});
});

export default Yup;

0 comments on commit b7fdf05

Please sign in to comment.