From e48f1620122456fbdee9191076b0010617efb498 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Fri, 26 Apr 2024 02:04:01 +0400 Subject: [PATCH 01/81] wip: Use updated property naming & Add typecheck task --- components/ContractInfo.tsx | 35 ++++++++----------- .../NavBar/components/PendingVerification.tsx | 6 ++-- components/containers/ContractsContainer.tsx | 4 +-- .../ConfirmVerificationDialog/index.tsx | 8 ++--- .../components/Description.tsx | 10 +++--- .../ViewProviderDialog/components/Header.tsx | 25 ++++++------- .../components/NextProviders.tsx | 4 +-- .../pages/admin/AdminReviewChecksSection.tsx | 2 +- components/pages/home/CompletedSection.tsx | 2 +- .../components/RecentChecks.tsx | 2 +- .../pages/home/PendingVerificationSection.tsx | 2 +- hooks/store/useProviders.ts | 2 +- hooks/useFilteredProviders.ts | 10 ++---- hooks/useGetProviderById.ts | 2 +- hooks/useProviderStatusChecker.ts | 2 +- hooks/useVerifiedProviderSuccess.ts | 2 +- package.json | 3 +- pages/account-info/index.tsx | 2 +- pages/add-stamp/index.tsx | 2 +- .../sybil.nadabot/interfaces/providers.ts | 2 +- services/web3/addMultipleStamps.ts | 2 +- 21 files changed, 57 insertions(+), 72 deletions(-) diff --git a/components/ContractInfo.tsx b/components/ContractInfo.tsx index c4ce752..58bf80a 100644 --- a/components/ContractInfo.tsx +++ b/components/ContractInfo.tsx @@ -71,7 +71,7 @@ export default function ContractInfo({ // Call add_stamp method if (providerInfo.is_user_a_human) { try { - await contract.add_stamp(providerInfo.provider_id); + await contract.add_stamp(providerInfo.id); } catch (error) { console.error(error); } @@ -81,7 +81,7 @@ export default function ContractInfo({ isPreview, showSpinner, hideSpinner, - providerInfo.provider_id, + providerInfo.id, providerInfo.is_user_a_human, ]); @@ -93,16 +93,11 @@ export default function ContractInfo({ } // Save provider to check if the user is now a human there - saveProvider(providerInfo.provider_id); + saveProvider(providerInfo.id); // Open up the external URL window.open(providerInfo.external_url, "_blank"); - }, [ - isPreview, - providerInfo.external_url, - providerInfo.provider_id, - saveProvider, - ]); + }, [isPreview, providerInfo.external_url, providerInfo.id, saveProvider]); const [points, setPoints] = useState(providerInfo.default_weight); const [previousPoints, setPreviousPoints] = useState( @@ -132,7 +127,7 @@ export default function ContractInfo({ (async () => { setUpdating(true); await contract.update_provider({ - provider_id: providerInfo.provider_id, + provider_id: providerInfo.id, default_weight: debouncedPoints, }); setUpdating(false); @@ -140,7 +135,7 @@ export default function ContractInfo({ // Update this provider withing store updateProvider({ - provider_id: providerInfo.provider_id, + provider_id: providerInfo.id, default_weight: debouncedPoints, }); })(); @@ -149,7 +144,7 @@ export default function ContractInfo({ debouncedPoints, points, previousPoints, - providerInfo.provider_id, + providerInfo.id, isPreview, updateProvider, ]); @@ -171,11 +166,11 @@ export default function ContractInfo({ setUpdating(true); if (!isProviderActive) { await contract.admin_activate_provider({ - provider_id: providerInfo.provider_id, + provider_id: providerInfo.id, default_weight: providerInfo.default_weight || 0, }); updateProvider({ - provider_id: providerInfo.provider_id, + provider_id: providerInfo.id, default_weight: providerInfo.default_weight || 0, status: ProviderStatus.Active, }); @@ -189,17 +184,17 @@ export default function ContractInfo({ }); } else { await contract.admin_deactivate_provider({ - provider_id: providerInfo.provider_id, + provider_id: providerInfo.id, }); updateProvider({ - provider_id: providerInfo.provider_id, + provider_id: providerInfo.id, status: ProviderStatus.Deactivated, }); } setUpdating(false); }, [ providerInfo.default_weight, - providerInfo.provider_id, + providerInfo.id, updateProvider, router, showSnackbar, @@ -223,7 +218,7 @@ export default function ContractInfo({ const openViewProviderDialog = useCallback(() => { openDialog({ dialog: DIALOGS.ViewProvider, - props: { providerId: providerInfo.provider_id }, + props: { providerId: providerInfo.id }, }); // Set route for this provider view @@ -231,8 +226,8 @@ export default function ContractInfo({ const hasPreviousQuery = updatedQueryBody.length > 0; const updatedPath = `${router.pathname}${updatedQueryBody}${hasPreviousQuery ? "&" : "?"}`; - router.replace(`${updatedPath}viewStamp=${providerInfo.provider_id}`); - }, [openDialog, providerInfo.provider_id, router]); + router.replace(`${updatedPath}viewStamp=${providerInfo.id}`); + }, [openDialog, providerInfo.id, router]); return ( { isLoading(true); try { - await add_stamp(providerInfo.provider_id); + await add_stamp(providerInfo.id); } catch (error) { console.error(error); } isLoading(false); - }, [providerInfo.provider_id]); + }, [providerInfo.id]); return ( ( 1} diff --git a/components/containers/ContractsContainer.tsx b/components/containers/ContractsContainer.tsx index d7f1b23..0209fd2 100644 --- a/components/containers/ContractsContainer.tsx +++ b/components/containers/ContractsContainer.tsx @@ -103,7 +103,7 @@ export default function ContractsContainer({ > {filteredProviders.map((provider) => ( @@ -123,7 +123,7 @@ export default function ContractsContainer({ > {filteredProviders.map((provider) => ( diff --git a/components/dialogs/ConfirmVerificationDialog/index.tsx b/components/dialogs/ConfirmVerificationDialog/index.tsx index 65c7098..dd3ca4f 100644 --- a/components/dialogs/ConfirmVerificationDialog/index.tsx +++ b/components/dialogs/ConfirmVerificationDialog/index.tsx @@ -45,15 +45,13 @@ export default function ConfirmVerificationDialog({ useEffect(() => { if (props?.providerId && activeIsHuman) { const _provider = activeIsHuman.find( - (prov) => prov.provider_id === props.providerId, + (prov) => prov.id === props.providerId, ); setProvider(_provider); // Build provider link if (_provider) { - setProviderLink( - `${window.location.origin}/?viewStamp=${_provider.provider_id}`, - ); + setProviderLink(`${window.location.origin}/?viewStamp=${_provider.id}`); } } }, [props?.providerId, activeIsHuman]); @@ -62,7 +60,7 @@ export default function ConfirmVerificationDialog({ if (provider) { isLoading(true); try { - await add_stamp(provider.provider_id); + await add_stamp(provider.id); } catch (error) { console.error(error); } diff --git a/components/dialogs/ViewProviderDialog/components/Description.tsx b/components/dialogs/ViewProviderDialog/components/Description.tsx index da7e590..216d285 100644 --- a/components/dialogs/ViewProviderDialog/components/Description.tsx +++ b/components/dialogs/ViewProviderDialog/components/Description.tsx @@ -44,7 +44,7 @@ export default function Description({ providerInfo }: Props) { (async () => { setUpdating(true); await contract.update_provider({ - provider_id: providerInfo.provider_id, + provider_id: providerInfo.id, default_weight: debouncedPoints, }); setUpdating(false); @@ -52,7 +52,7 @@ export default function Description({ providerInfo }: Props) { // Update this provider withing store updateProvider({ - provider_id: providerInfo.provider_id, + provider_id: providerInfo.id, default_weight: debouncedPoints, }); })(); @@ -62,15 +62,15 @@ export default function Description({ providerInfo }: Props) { // Users for Stamp const [verifiedUsers, setVerifiedUsers] = useState(); useEffect(() => { - if (providerInfo?.provider_id) { + if (providerInfo?.id) { (async () => { const usersForStamp = await contract.get_users_for_stamp({ - provider_id: providerInfo.provider_id, + provider_id: providerInfo.id, }); setVerifiedUsers(usersForStamp); })(); } - }, [providerInfo?.provider_id]); + }, [providerInfo?.id]); return ( { let hasStamp = false; stamps.forEach((stamp) => { - if ( - !hasStamp && - stamp.provider.provider_id === providerInfo?.provider_id - ) { + if (!hasStamp && stamp.provider.id === providerInfo?.id) { hasStamp = true; } }); @@ -71,11 +68,11 @@ export default function Header({ providerInfo }: Props) { setUpdating(true); if (!isProviderActive) { await contract.admin_activate_provider({ - provider_id: providerInfo!.provider_id, + provider_id: providerInfo!.id, default_weight: providerInfo!.default_weight || 0, }); updateProvider({ - provider_id: providerInfo!.provider_id, + provider_id: providerInfo!.id, default_weight: providerInfo!.default_weight || 0, status: ProviderStatus.Active, }); @@ -89,10 +86,10 @@ export default function Header({ providerInfo }: Props) { }); } else { await contract.admin_deactivate_provider({ - provider_id: providerInfo!.provider_id, + provider_id: providerInfo!.id, }); updateProvider({ - provider_id: providerInfo!.provider_id, + provider_id: providerInfo!.id, status: ProviderStatus.Deactivated, }); } @@ -112,9 +109,9 @@ export default function Header({ providerInfo }: Props) { const verifyHandler = useCallback(async () => { showSpinner(); // If so, then, call add_stamp method - if (providerInfo?.is_user_a_human && providerInfo?.provider_id) { + if (providerInfo?.is_user_a_human && providerInfo?.id) { try { - await contract.add_stamp(providerInfo.provider_id); + await contract.add_stamp(providerInfo.id); } catch (error) { console.error(error); } @@ -122,7 +119,7 @@ export default function Header({ providerInfo }: Props) { hideSpinner(); } }, [ - providerInfo?.provider_id, + providerInfo?.id, providerInfo?.is_user_a_human, showSpinner, hideSpinner, @@ -131,14 +128,14 @@ export default function Header({ providerInfo }: Props) { const { saveProvider } = useProviderStatusChecker(); const getCheckHandler = useCallback(() => { - if (providerInfo?.external_url && providerInfo.provider_id) { + if (providerInfo?.external_url && providerInfo.id) { // Save provider to check if the user is now a human there - saveProvider(providerInfo.provider_id); + saveProvider(providerInfo.id); // Open up the external URL window.open(providerInfo?.external_url, "_blank"); } - }, [providerInfo?.external_url, providerInfo?.provider_id, saveProvider]); + }, [providerInfo?.external_url, providerInfo?.id, saveProvider]); const openContractAddress = useCallback(() => { const explorerURL = diff --git a/components/dialogs/ViewProviderDialog/components/NextProviders.tsx b/components/dialogs/ViewProviderDialog/components/NextProviders.tsx index 77e23cf..1e4eac9 100644 --- a/components/dialogs/ViewProviderDialog/components/NextProviders.tsx +++ b/components/dialogs/ViewProviderDialog/components/NextProviders.tsx @@ -13,7 +13,7 @@ type Props = { export default function NextProviders({ providerInfo }: Props) { const { active, deactivated } = useFilteredProviders({ - skipProviderId: providerInfo!.provider_id, + skipProviderId: providerInfo!.id, }); const { maxWidth805 } = useBreakPoints(); @@ -39,7 +39,7 @@ export default function NextProviders({ providerInfo }: Props) { > {providers.slice(0, 3).map((provider) => ( diff --git a/components/pages/admin/AdminReviewChecksSection.tsx b/components/pages/admin/AdminReviewChecksSection.tsx index ad27339..9ee26a3 100644 --- a/components/pages/admin/AdminReviewChecksSection.tsx +++ b/components/pages/admin/AdminReviewChecksSection.tsx @@ -164,7 +164,7 @@ export default function AdminReviewChecksSection() { > {filteredProviders.map((provider) => ( diff --git a/components/pages/home/CompletedSection.tsx b/components/pages/home/CompletedSection.tsx index 651cd58..f3c5690 100644 --- a/components/pages/home/CompletedSection.tsx +++ b/components/pages/home/CompletedSection.tsx @@ -43,7 +43,7 @@ export default function CompletedSection() { = 3}> {stamps.map((stamp) => ( diff --git a/components/pages/home/ExploreSection/components/RecentChecks.tsx b/components/pages/home/ExploreSection/components/RecentChecks.tsx index e8825c4..b6a57da 100644 --- a/components/pages/home/ExploreSection/components/RecentChecks.tsx +++ b/components/pages/home/ExploreSection/components/RecentChecks.tsx @@ -49,7 +49,7 @@ export default function RecentChecks() { {/* List - Max 3 items */} {stamps.slice(0, 3).map((stamp, index) => ( = 3}> {activeIsHuman.map((provider) => ( ()( // update provider updateProvider: (newProviderInfo: UpdateProviderInput) => { const updatedProviders = get().providers.map((provider) => { - if (provider.provider_id === newProviderInfo.provider_id) { + if (provider.id === newProviderInfo.provider_id) { provider = { ...provider, ...newProviderInfo }; } return provider; diff --git a/hooks/useFilteredProviders.ts b/hooks/useFilteredProviders.ts index 43bfec9..89bc75e 100644 --- a/hooks/useFilteredProviders.ts +++ b/hooks/useFilteredProviders.ts @@ -112,19 +112,13 @@ const useFilteredProviders = ({ skipProviderId, sortMethod }: Props) => { // Check if current user has a stamp for this provider, if so, skip it let hasStamp = false; stamps.forEach((stamp) => { - if ( - !hasStamp && - stamp.provider.provider_id === provider.provider_id - ) { + if (!hasStamp && stamp.provider.id === provider.id) { hasStamp = true; } }); // NOTE: If it's /admin page, should show all providers - if ( - provider.provider_id !== skipProviderId && - (!hasStamp || isAdminPage) - ) { + if (provider.id !== skipProviderId && (!hasStamp || isAdminPage)) { // Active if (provider.status === ProviderStatus.Active) { tempActive.push(provider); diff --git a/hooks/useGetProviderById.ts b/hooks/useGetProviderById.ts index b515165..de6d4bb 100644 --- a/hooks/useGetProviderById.ts +++ b/hooks/useGetProviderById.ts @@ -10,7 +10,7 @@ const useGetProviderById = (providerId?: string) => { useEffect(() => { if (providerId) { - setProvider(all.find((provider) => provider.provider_id === providerId)); + setProvider(all.find((provider) => provider.id === providerId)); } }, [providerId, all]); diff --git a/hooks/useProviderStatusChecker.ts b/hooks/useProviderStatusChecker.ts index 613316c..02d4106 100644 --- a/hooks/useProviderStatusChecker.ts +++ b/hooks/useProviderStatusChecker.ts @@ -28,7 +28,7 @@ const useProviderStatusChecker = () => { if (providerId && activeIsHuman.length > 0) { const foundStamp = activeIsHuman.find( - (provider) => provider.provider_id === providerId, + (provider) => provider.id === providerId, ); isHuman = (foundStamp && foundStamp.is_user_a_human) || false; diff --git a/hooks/useVerifiedProviderSuccess.ts b/hooks/useVerifiedProviderSuccess.ts index e05d2f7..7a5bb2d 100644 --- a/hooks/useVerifiedProviderSuccess.ts +++ b/hooks/useVerifiedProviderSuccess.ts @@ -28,7 +28,7 @@ const useVerifiedProviderSuccess = () => { if (provider && !done) { openDialog({ dialog: DIALOGS.ViewProvider, - props: { providerId: provider.provider_id }, + props: { providerId: provider.id }, }); showSnackbar({ diff --git a/package.json b/package.json index 300a593..2a40312 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { + "typecheck": "tsc --noEmit", "dev": "next dev", "build": "next build", "start": "next start", @@ -60,4 +61,4 @@ "prettier": "^3.2.5", "typescript": "^5" } -} +} \ No newline at end of file diff --git a/pages/account-info/index.tsx b/pages/account-info/index.tsx index 95f5375..0676c0c 100644 --- a/pages/account-info/index.tsx +++ b/pages/account-info/index.tsx @@ -65,7 +65,7 @@ export default function AccountInfoPage() { () => userStamps.map((stamp) => ( diff --git a/pages/add-stamp/index.tsx b/pages/add-stamp/index.tsx index bf8776e..e57b9b3 100644 --- a/pages/add-stamp/index.tsx +++ b/pages/add-stamp/index.tsx @@ -375,7 +375,7 @@ export default function AddStampPage() { stamp_count: 0, account_id_arg_name: "account_id", status: ProviderStatus.Active, - provider_id: "", + id: "", icon_url: filesContent[0]?.content, submitted_by: walletApi?.accounts[0]?.accountId, name: formik.values.title, diff --git a/services/contracts/sybil.nadabot/interfaces/providers.ts b/services/contracts/sybil.nadabot/interfaces/providers.ts index 1843e9c..03e6f0d 100644 --- a/services/contracts/sybil.nadabot/interfaces/providers.ts +++ b/services/contracts/sybil.nadabot/interfaces/providers.ts @@ -34,7 +34,7 @@ export interface Provider { export interface ProviderExternal { // Provider ID - provider_id: string; + id: string; // Contract ID of the external contract that is the source of this provider contract_id: string; // Method name of the external contract that is the source of this provider diff --git a/services/web3/addMultipleStamps.ts b/services/web3/addMultipleStamps.ts index 7af6d90..cc5fc0e 100644 --- a/services/web3/addMultipleStamps.ts +++ b/services/web3/addMultipleStamps.ts @@ -17,7 +17,7 @@ const addMultipleStamps = (providers: ProviderExternalWithIsHuman[]) => { listOfTransactions.push( buildTransaction("add_stamp", { args: { - provider_id: provider.provider_id, + provider_id: provider.id, }, gas: FULL_TGAS, deposit: TWO_HUNDREDTHS_NEAR, From a0bbc5fa7c169d3c4ed2abbd4eb5110919a765f5 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Fri, 26 Apr 2024 17:51:56 +0400 Subject: [PATCH 02/81] Bump Naxios --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1bd15bb..e81d71d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@near-wallet-selector/welldone-wallet": "^8.9.5", "@near-wallet-selector/xdefi": "^8.9.5", "@uidotdev/usehooks": "^2.4.1", - "@wpdas/naxios": "^2.0.1", + "@wpdas/naxios": "^2.1.0", "copy-to-clipboard": "^3.3.3", "formik": "^2.4.5", "fuse.js": "^7.0.0", @@ -4601,9 +4601,9 @@ "dev": true }, "node_modules/@wpdas/naxios": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@wpdas/naxios/-/naxios-2.0.1.tgz", - "integrity": "sha512-zxEx4oWxLbcblHjQOjA4V8Gcir59JWC3CtfmxaOtMbHDcAB8USi4eslUyfMBScm6nwApwEj9X/BmQpRoE2NTew==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@wpdas/naxios/-/naxios-2.1.0.tgz", + "integrity": "sha512-9nYUiU7km81KHZhCc4pRI3apcy7teLfxBvjigHvDDMGHMqz9/8kOq2ATi7uFJHrInNEWCXn/k3i8qjM7wortsw==", "dependencies": { "@near-wallet-selector/core": "^8.9.1", "@near-wallet-selector/modal-ui": "^8.9.1", diff --git a/package.json b/package.json index 2a40312..2c0c557 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@near-wallet-selector/welldone-wallet": "^8.9.5", "@near-wallet-selector/xdefi": "^8.9.5", "@uidotdev/usehooks": "^2.4.1", - "@wpdas/naxios": "^2.0.1", + "@wpdas/naxios": "^2.1.0", "copy-to-clipboard": "^3.3.3", "formik": "^2.4.5", "fuse.js": "^7.0.0", From 547a8cf937c319ef4362f17407a9749902e331af Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Tue, 30 Apr 2024 07:23:12 +0400 Subject: [PATCH 03/81] wip: Sync contract field names --- components/ContractInfo.tsx | 2 +- components/NavBar/components/PendingVerification.tsx | 4 +++- .../home/ExploreSection/components/RecentChecks.tsx | 2 +- hooks/useFilteredProviders.ts | 2 +- .../contracts/sybil.nadabot/interfaces/providers.ts | 12 ++++++++---- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/components/ContractInfo.tsx b/components/ContractInfo.tsx index 58bf80a..a7d89d4 100644 --- a/components/ContractInfo.tsx +++ b/components/ContractInfo.tsx @@ -286,7 +286,7 @@ export default function ContractInfo({ className="ellipsis" color={colors.PRIMARY} > - {providerInfo.name || "Contract Title"} + {providerInfo.provider_name || "Contract Title"} diff --git a/components/NavBar/components/PendingVerification.tsx b/components/NavBar/components/PendingVerification.tsx index d9c281e..c7ddc6a 100644 --- a/components/NavBar/components/PendingVerification.tsx +++ b/components/NavBar/components/PendingVerification.tsx @@ -57,7 +57,9 @@ function Item({ lineHeight="normal" mr={maxWidth430 ? 0 : 2} > - {maxWidth1144 ? providerInfo.name : truncate(providerInfo.name, 15)} + {maxWidth1144 + ? providerInfo.provider_name + : truncate(providerInfo.provider_name, 15)} { "font-weight: 600; background-color: #000000; color: #FFFFFF; padding: 4px 2px; margin-top: 2px;"; const contentErrorStyle = "font-weight: normal;"; console.error( - `Error checking if user is human. \n%cProvider Name:%c ${provider.name} \n%cContractID:%c ${provider.contract_id} \n%cMethod Name:%c ${provider.method_name}. \n%cError Body:%c ${error}`, + `Error checking if user is human. \n%cProvider Name:%c ${provider.provider_name} \n%cContractID:%c ${provider.contract_id} \n%cMethod Name:%c ${provider.method_name}. \n%cError Body:%c ${error}`, titleErrorStyle, contentErrorStyle, titleErrorStyle, diff --git a/services/contracts/sybil.nadabot/interfaces/providers.ts b/services/contracts/sybil.nadabot/interfaces/providers.ts index 03e6f0d..5120fa1 100644 --- a/services/contracts/sybil.nadabot/interfaces/providers.ts +++ b/services/contracts/sybil.nadabot/interfaces/providers.ts @@ -7,7 +7,7 @@ export enum ProviderStatus { export interface Provider { // NB: contract address/ID and method name are contained in the Provider's ID (see `ProviderId`) so do not need to be stored here /// Name of the provider, e.g. "I Am Human" - name: string; + provider_name: string; /// Description of the provider description?: string; /// Status of the provider @@ -30,6 +30,8 @@ export interface Provider { submitted_at_ms?: number; /// Total number of times this provider has been used successfully stamp_count: number; + /// Milliseconds that stamps from this provider are valid for before they expire + stamp_validity_ms?: number; } export interface ProviderExternal { @@ -42,7 +44,7 @@ export interface ProviderExternal { /// Account ID arg name account_id_arg_name: string; // Name of the provider, e.g. "I Am Human" - name: string; + provider_name: string; // Description of the provider description?: string; /// Status of the provider @@ -65,6 +67,8 @@ export interface ProviderExternal { submitted_at_ms: number; // Total number of times this provider has been used successfully stamp_count: number; + /// Milliseconds that stamps from this provider are valid for before they expire + stamp_validity_ms?: number; } export type ProviderExternalWithIsHuman = ProviderExternal & { @@ -75,7 +79,7 @@ export interface RegisterProviderInput { contract_id: string; method_name: string; account_id_arg_name?: string; - name: string; + provider_name: string; description?: string; gas?: number; tags?: string[]; @@ -85,7 +89,7 @@ export interface RegisterProviderInput { export interface UpdateProviderInput { provider_id: string; - name?: string; + provider_name?: string; description?: string; gas?: number; tags?: string[]; From ab747c6b48de6823c8edaa3e821e5fda9c4cb7d7 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Tue, 30 Apr 2024 17:38:16 +0400 Subject: [PATCH 04/81] wip: Sync contract field names --- components/NavBar/components/UserDropbox.tsx | 2 +- components/dialogs/ConfirmVerificationDialog/index.tsx | 6 +++--- components/dialogs/ViewProviderDialog/components/Header.tsx | 2 +- pages/account-info/index.tsx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/components/NavBar/components/UserDropbox.tsx b/components/NavBar/components/UserDropbox.tsx index fbcf0fb..45f47f6 100644 --- a/components/NavBar/components/UserDropbox.tsx +++ b/components/NavBar/components/UserDropbox.tsx @@ -103,7 +103,7 @@ const UserDropbox = () => { - {truncate(profileInfo?.name || accountId, 14)} + {truncate(profileInfo?.provider_name || accountId, 14)} {isAdmin && router.route !== Routes.ADMIN_HOME && ( diff --git a/components/dialogs/ConfirmVerificationDialog/index.tsx b/components/dialogs/ConfirmVerificationDialog/index.tsx index dd3ca4f..2dcb283 100644 --- a/components/dialogs/ConfirmVerificationDialog/index.tsx +++ b/components/dialogs/ConfirmVerificationDialog/index.tsx @@ -71,7 +71,7 @@ export default function ConfirmVerificationDialog({ const shareTwitter = () => { if (providerLink) { window.open( - `https://twitter.com/intent/tweet?url=${encodeURIComponent(providerLink)}&text=${encodeURIComponent(`I just got my "${provider?.name}" check using #NadaBot. Take a look here: `)}`, + `https://twitter.com/intent/tweet?url=${encodeURIComponent(providerLink)}&text=${encodeURIComponent(`I just got my "${provider?.provider_name}" check using #NadaBot. Take a look here: `)}`, "_blank", ); } @@ -80,7 +80,7 @@ export default function ConfirmVerificationDialog({ const shareTelegram = () => { if (providerLink) { window.open( - `https://telegram.me/share/url?url=${encodeURIComponent(providerLink)}&text=${encodeURIComponent(`I just got my "${provider?.name}" check using NadaBot. Take a look! `)}`, + `https://telegram.me/share/url?url=${encodeURIComponent(providerLink)}&text=${encodeURIComponent(`I just got my "${provider?.provider_name}" check using NadaBot. Take a look! `)}`, "_blank", ); } @@ -108,7 +108,7 @@ export default function ConfirmVerificationDialog({ color={colors.PRIMARY} > You have done{" "} - {provider?.name} check.{" "} + {provider?.provider_name} check.{" "}
Now add this check to Nada Bot contract. diff --git a/components/dialogs/ViewProviderDialog/components/Header.tsx b/components/dialogs/ViewProviderDialog/components/Header.tsx index 66edbaa..fdaa292 100644 --- a/components/dialogs/ViewProviderDialog/components/Header.tsx +++ b/components/dialogs/ViewProviderDialog/components/Header.tsx @@ -183,7 +183,7 @@ export default function Header({ providerInfo }: Props) { textAlign={maxWidth805 ? "center" : "left"} mb={maxWidth805 ? 2 : 0} > - {truncate(providerInfo?.name || "", 28)} + {truncate(providerInfo?.provider_name || "", 28)}
{ // Profile info const profileInfo = await get_user_profile({ accountId }); - setUsername(profileInfo?.name || accountId); + setUsername(profileInfo?.provider_name || accountId); // Is Human Check const response = await contract.get_human_score({ From 581022df7644ece5e9079969c867a69187b93fd0 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Thu, 2 May 2024 12:08:15 +0400 Subject: [PATCH 05/81] wip --- components/ContractInfo.tsx | 90 +++++++++++------ components/NavBar/components/UserDropbox.tsx | 2 +- .../ConfirmVerificationDialog/index.tsx | 5 +- .../pages/admin/AdminReviewChecksSection.tsx | 6 +- components/pages/home/ChecksSection.tsx | 2 + components/ui/AddFilterSearchInput.tsx | 2 +- components/ui/Input.tsx | 97 ++++++++++++------- components/ui/RegularInput.tsx | 21 ++-- pages/account-info/index.tsx | 2 +- pages/add-stamp/index.tsx | 4 +- .../sybil.nadabot/interfaces/providers.ts | 6 +- 11 files changed, 154 insertions(+), 83 deletions(-) diff --git a/components/ContractInfo.tsx b/components/ContractInfo.tsx index a7d89d4..888a024 100644 --- a/components/ContractInfo.tsx +++ b/components/ContractInfo.tsx @@ -1,3 +1,4 @@ +import AutoDeleteOutlinedIcon from "@mui/icons-material/AutoDeleteOutlined"; import CheckIcon from "@mui/icons-material/Check"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { @@ -34,6 +35,7 @@ import ButtonContainer from "./containers/ButtonContainer"; import CustomAvatar from "./ui/CustomAvatar"; import CustomButton from "./ui/CustomButton"; import CustomCircularProgress from "./ui/CustomCircularProgress"; +import Input from "./ui/Input"; type Props = { hidePoints?: boolean; @@ -331,40 +333,68 @@ export default function ContractInfo({ {/* Edit Points */} {isAdmin && ( - - - - - Edit Points - + + + + + + Edit Points + + + + + + + + {previousPoints} pts + + + + + {updating ? ( + + ) : ( + + changePointsHandler(newValue as number) + } + /> + )} + + + + } + label="Edit Expiry" + labelDecoration={ - - - - {previousPoints} pts + } + type="number" + min={0} + placeholder="30" + rightComponent={ + + Days - - - {updating ? ( - - ) : ( - - changePointsHandler(newValue as number) - } - /> - )} + } + /> )} diff --git a/components/NavBar/components/UserDropbox.tsx b/components/NavBar/components/UserDropbox.tsx index 45f47f6..fbcf0fb 100644 --- a/components/NavBar/components/UserDropbox.tsx +++ b/components/NavBar/components/UserDropbox.tsx @@ -103,7 +103,7 @@ const UserDropbox = () => { - {truncate(profileInfo?.provider_name || accountId, 14)} + {truncate(profileInfo?.name || accountId, 14)} {isAdmin && router.route !== Routes.ADMIN_HOME && ( diff --git a/components/dialogs/ConfirmVerificationDialog/index.tsx b/components/dialogs/ConfirmVerificationDialog/index.tsx index 2dcb283..ed23d58 100644 --- a/components/dialogs/ConfirmVerificationDialog/index.tsx +++ b/components/dialogs/ConfirmVerificationDialog/index.tsx @@ -107,8 +107,9 @@ export default function ConfirmVerificationDialog({ fontSize={20} color={colors.PRIMARY} > - You have done{" "} - {provider?.provider_name} check.{" "} + {"You have done "} + {provider?.provider_name} + {"check."}
Now add this check to Nada Bot contract. diff --git a/components/pages/admin/AdminReviewChecksSection.tsx b/components/pages/admin/AdminReviewChecksSection.tsx index 9ee26a3..f795483 100644 --- a/components/pages/admin/AdminReviewChecksSection.tsx +++ b/components/pages/admin/AdminReviewChecksSection.tsx @@ -84,6 +84,10 @@ export default function AdminReviewChecksSection() { router.push(Routes.ADD_STAMP); }, [router]); + console.log( + (filteredProviders ?? []).map(({ stamp_validity_ms }) => stamp_validity_ms), + ); + return ( } + rightComponent={} onChange={(e) => setSearchPattern(e.target.value)} /> 0; + console.log(active.map(({ stamp_validity_ms }) => stamp_validity_ms)); + return ( onChange(e.target.value)} placeholder="Search" - rightComponent={} + rightComponent={} /> diff --git a/components/ui/Input.tsx b/components/ui/Input.tsx index 40dcea9..862ce48 100644 --- a/components/ui/Input.tsx +++ b/components/ui/Input.tsx @@ -1,31 +1,42 @@ import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; -import { Stack, SxProps, Theme, Typography } from "@mui/material"; -import { ChangeEvent, HTMLInputTypeAttribute } from "react"; +import { Stack, Typography } from "@mui/material"; -import RegularInput from "@nadabot/components/ui/RegularInput"; +import RegularInput, { + RegularInputProps, +} from "@nadabot/components/ui/RegularInput"; import colors from "@nadabot/theme/colors"; -type Props = { +type Props = Pick< + RegularInputProps, + | "autoComplete" + | "defaultValue" + | "fontSize" + | "disabled" + | "integersOnly" + | "min" + | "max" + | "name" + | "onChange" + | "leftComponent" + | "rightComponent" + | "sx" + | "type" +> & { label?: string; + labelDecoration?: JSX.Element; placeholder: string; info?: string; optional?: boolean; - type?: HTMLInputTypeAttribute; - sx?: SxProps; - disabled?: boolean; errorMessage?: string; - onChange?: (e: ChangeEvent) => void; - name?: string; - defaultValue?: string | number | readonly string[]; - autoComplete?: boolean; - integersOnly?: boolean; - min?: string | number; - max?: string | number; }; export default function Input({ + fontSize, label, + labelDecoration, placeholder, + leftComponent, + rightComponent, info, optional, type, @@ -41,30 +52,39 @@ export default function Input({ max, }: Props) { return ( - - - {label && ( - - {label} - - )} - {optional && ( - - (optional) - - )} + + + + {label && ( + + {label} + + )} + + {optional && ( + + (optional) + + )} + + + {labelDecoration} + + {info && ( - + + )} + {/* Show error only if a Info is not provided */} {errorMessage && ( - + {errorMessage} )} diff --git a/components/ui/RegularInput.tsx b/components/ui/RegularInput.tsx index b2b6332..d850447 100644 --- a/components/ui/RegularInput.tsx +++ b/components/ui/RegularInput.tsx @@ -5,9 +5,11 @@ import colors from "@nadabot/theme/colors"; import CustomInput from "./CustomInput"; -type Props = { +export type RegularInputProps = { + fontSize?: number | string; placeholder?: string; enableShadow?: boolean; + leftComponent?: JSX.Element; rightComponent?: JSX.Element; type?: HTMLInputTypeAttribute; sx?: SxProps; @@ -23,8 +25,10 @@ type Props = { }; const RegularInput = ({ + fontSize, placeholder, enableShadow, + leftComponent, rightComponent, type, sx, @@ -37,17 +41,17 @@ const RegularInput = ({ integersOnly, min, max, -}: Props) => { +}: RegularInputProps) => { return ( + {leftComponent} + { @@ -80,6 +87,7 @@ const RegularInput = ({ event.preventDefault(); } }, + onPaste: (event) => { const pasteData = event.clipboardData.getData("text"); if (pasteData) { @@ -89,6 +97,7 @@ const RegularInput = ({ } : {})} /> + {rightComponent} diff --git a/pages/account-info/index.tsx b/pages/account-info/index.tsx index bd6df24..0676c0c 100644 --- a/pages/account-info/index.tsx +++ b/pages/account-info/index.tsx @@ -42,7 +42,7 @@ export default function AccountInfoPage() { (async () => { // Profile info const profileInfo = await get_user_profile({ accountId }); - setUsername(profileInfo?.provider_name || accountId); + setUsername(profileInfo?.name || accountId); // Is Human Check const response = await contract.get_human_score({ diff --git a/pages/add-stamp/index.tsx b/pages/add-stamp/index.tsx index e57b9b3..fd2d0ff 100644 --- a/pages/add-stamp/index.tsx +++ b/pages/add-stamp/index.tsx @@ -177,7 +177,7 @@ export default function AddStampPage() { contract_id: contractName, method_name: method, account_id_arg_name: accountIdArgName || DEFAULT_ARG_NAME, - name: title, + provider_name: title, description, gas: providerGas, icon_url: pinataServices.buildFileURL(iconImageCID), @@ -378,7 +378,7 @@ export default function AddStampPage() { id: "", icon_url: filesContent[0]?.content, submitted_by: walletApi?.accounts[0]?.accountId, - name: formik.values.title, + provider_name: formik.values.title, description: formik.values.description, contract_id: formik.values.contractName, method_name: formik.values.method, diff --git a/services/contracts/sybil.nadabot/interfaces/providers.ts b/services/contracts/sybil.nadabot/interfaces/providers.ts index 5120fa1..63b0634 100644 --- a/services/contracts/sybil.nadabot/interfaces/providers.ts +++ b/services/contracts/sybil.nadabot/interfaces/providers.ts @@ -31,7 +31,7 @@ export interface Provider { /// Total number of times this provider has been used successfully stamp_count: number; /// Milliseconds that stamps from this provider are valid for before they expire - stamp_validity_ms?: number; + stamp_validity_ms?: number | null; } export interface ProviderExternal { @@ -68,7 +68,7 @@ export interface ProviderExternal { // Total number of times this provider has been used successfully stamp_count: number; /// Milliseconds that stamps from this provider are valid for before they expire - stamp_validity_ms?: number; + stamp_validity_ms?: number | null; } export type ProviderExternalWithIsHuman = ProviderExternal & { @@ -80,6 +80,7 @@ export interface RegisterProviderInput { method_name: string; account_id_arg_name?: string; provider_name: string; + stamp_validity_ms?: number; description?: string; gas?: number; tags?: string[]; @@ -90,6 +91,7 @@ export interface RegisterProviderInput { export interface UpdateProviderInput { provider_id: string; provider_name?: string; + stamp_validity_ms?: number; description?: string; gas?: number; tags?: string[]; From 8c687c686c942371a235be70e8920bfbc9e4a589 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Thu, 2 May 2024 12:23:33 +0400 Subject: [PATCH 06/81] wip: Adjust styles --- components/ContractInfo.tsx | 6 ++---- components/ui/Input.tsx | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/components/ContractInfo.tsx b/components/ContractInfo.tsx index 888a024..39b310e 100644 --- a/components/ContractInfo.tsx +++ b/components/ContractInfo.tsx @@ -336,10 +336,8 @@ export default function ContractInfo({ - - - Edit Points - + + Edit Points - + {label && ( Date: Fri, 3 May 2024 13:31:59 +0400 Subject: [PATCH 07/81] Implement expiry form logic --- components/ContractInfo.tsx | 66 ++++++++++++++----- components/ui/Input.tsx | 3 +- package-lock.json | 14 ++++ package.json | 3 +- .../sybil.nadabot/interfaces/providers.ts | 2 +- 5 files changed, 66 insertions(+), 22 deletions(-) diff --git a/components/ContractInfo.tsx b/components/ContractInfo.tsx index 39b310e..3bd724d 100644 --- a/components/ContractInfo.tsx +++ b/components/ContractInfo.tsx @@ -13,7 +13,8 @@ import { } from "@mui/material"; import { useDebounce } from "@uidotdev/usehooks"; import { useRouter } from "next/router"; -import { useCallback, useEffect, useState } from "react"; +import { ChangeEvent, useCallback, useEffect, useState } from "react"; +import { Temporal } from "temporal-polyfill"; import { DIALOGS } from "@nadabot/contexts/DialogsProvider"; import { useProviders } from "@nadabot/hooks/store/useProviders"; @@ -64,6 +65,28 @@ export default function ContractInfo({ const { openDialog } = useDialogs(); const { showSpinner, hideSpinner } = useSpinner(); + const [expiryMs, setExpiryMs] = useState( + providerInfo.stamp_validity_ms ?? null, + ); + + const expiryDays = + typeof expiryMs === "number" + ? Temporal.Duration.from({ milliseconds: expiryMs }).total("days") + : 0; + + const onExpiryChange = useCallback( + ({ target: { value } }: ChangeEvent) => + setExpiryMs( + typeof value === "string" + ? Temporal.Duration.from({ days: parseInt(value) }).total( + "milliseconds", + ) + : null, + ), + + [setExpiryMs], + ); + const verifyHandler = useCallback(async () => { if (isPreview) { return; @@ -204,16 +227,6 @@ export default function ContractInfo({ isProviderActive, ]); - // Active / De-Activate - const [activeLabel, setActiveLabel] = useState("Active"); - const activeMouseOverHandler = useCallback(() => { - setActiveLabel("De-Activate"); - }, []); - - const activeMouseOutHandler = useCallback(() => { - setActiveLabel("Active"); - }, []); - /** * Open up the View Provider Dialog */ @@ -268,6 +281,7 @@ export default function ContractInfo({ }} /> + {!hidePoints && ( @@ -305,6 +319,7 @@ export default function ContractInfo({ > {providerInfo.contract_id || "contract.name.near"} + } type="number" + integersOnly min={0} + fontSize={20} placeholder="30" + defaultValue={expiryDays} rightComponent={ Days } + onChange={onExpiryChange} /> + + + contract.update_provider({ + provider_id: providerInfo.id, + stamp_validity_ms: expiryMs, + }) + } + > + Save + )} @@ -414,6 +445,7 @@ export default function ContractInfo({ SUBMITTED BY + + + Verified @@ -461,14 +495,10 @@ export default function ContractInfo({ color="beige" bodySize="medium" onClick={switchActivation} - onMouseOver={activeMouseOverHandler} - onMouseOut={activeMouseOutHandler} sx={{ mt: maxWidth430 ? 2 : 0, px: 2, - ...(activeLabel === "De-Activate" && isProviderActive - ? { px: 1 } - : {}), + ...(isProviderActive ? { backgroundColor: @@ -489,7 +519,7 @@ export default function ContractInfo({ : {}), }} > - {isProviderActive ? activeLabel : "Activate"} + {isProviderActive ? "Deactivate" : "Activate"} ) : ( diff --git a/components/ui/Input.tsx b/components/ui/Input.tsx index 04befa5..7f3e69d 100644 --- a/components/ui/Input.tsx +++ b/components/ui/Input.tsx @@ -54,7 +54,7 @@ export default function Input({ return ( - + {label && ( (optional) diff --git a/package-lock.json b/package-lock.json index e81d71d..131e81b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,7 @@ "next": "14.0.4", "react": "^18", "react-dom": "^18", + "temporal-polyfill": "^0.2.4", "use-file-picker": "^2.1.1", "yup": "^1.3.3", "zustand": "^4.4.7" @@ -9415,6 +9416,19 @@ "node": ">=6" } }, + "node_modules/temporal-polyfill": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.2.4.tgz", + "integrity": "sha512-WA5p0CjQTkMjF9m8sP4wSYgpqI8m2d4q7wPUyaJOWhy4bI9mReLb2yGvTV4qf/DPMTe6H6M/Dig5KmTMB7ev6Q==", + "dependencies": { + "temporal-spec": "^0.2.4" + } + }, + "node_modules/temporal-spec": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.2.4.tgz", + "integrity": "sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ==" + }, "node_modules/term-size": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", diff --git a/package.json b/package.json index 2c0c557..fc74b4d 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "next": "14.0.4", "react": "^18", "react-dom": "^18", + "temporal-polyfill": "^0.2.4", "use-file-picker": "^2.1.1", "yup": "^1.3.3", "zustand": "^4.4.7" @@ -61,4 +62,4 @@ "prettier": "^3.2.5", "typescript": "^5" } -} \ No newline at end of file +} diff --git a/services/contracts/sybil.nadabot/interfaces/providers.ts b/services/contracts/sybil.nadabot/interfaces/providers.ts index 63b0634..b60ac42 100644 --- a/services/contracts/sybil.nadabot/interfaces/providers.ts +++ b/services/contracts/sybil.nadabot/interfaces/providers.ts @@ -91,7 +91,7 @@ export interface RegisterProviderInput { export interface UpdateProviderInput { provider_id: string; provider_name?: string; - stamp_validity_ms?: number; + stamp_validity_ms?: number | null; description?: string; gas?: number; tags?: string[]; From fd3875b15b771b94a714f9de67bee9c214962d2a Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Sat, 4 May 2024 07:32:00 +0400 Subject: [PATCH 08/81] wip: Create provider settings form --- .vscode/settings.json | 12 +- components/ContractInfo.tsx | 211 +++++--------------------------- components/ProviderSettings.tsx | 163 ++++++++++++++++++++++++ components/ui/CustomButton.tsx | 12 +- utils/time.ts | 11 ++ 5 files changed, 217 insertions(+), 192 deletions(-) create mode 100644 components/ProviderSettings.tsx create mode 100644 utils/time.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 715d4e3..5595e30 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,9 +1,13 @@ { "editor.formatOnSave": true, - "eslint.validate": ["typescript"], + "eslint.validate": [ + "typescript" + ], "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", "source.fixAll": "explicit" - } -} - + }, + "cSpell.words": [ + "nadabot" + ] +} \ No newline at end of file diff --git a/components/ContractInfo.tsx b/components/ContractInfo.tsx index 3bd724d..7f98c0a 100644 --- a/components/ContractInfo.tsx +++ b/components/ContractInfo.tsx @@ -1,20 +1,15 @@ -import AutoDeleteOutlinedIcon from "@mui/icons-material/AutoDeleteOutlined"; import CheckIcon from "@mui/icons-material/Check"; -import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Box, Button, Chip, - Slider, Stack, SxProps, Theme, Typography, } from "@mui/material"; -import { useDebounce } from "@uidotdev/usehooks"; import { useRouter } from "next/router"; -import { ChangeEvent, useCallback, useEffect, useState } from "react"; -import { Temporal } from "temporal-polyfill"; +import { useCallback, useState } from "react"; import { DIALOGS } from "@nadabot/contexts/DialogsProvider"; import { useProviders } from "@nadabot/hooks/store/useProviders"; @@ -33,10 +28,10 @@ import colors from "@nadabot/theme/colors"; import removeViewStampFromURLQuery from "@nadabot/utils/removeViewStampFromURLQuery"; import ButtonContainer from "./containers/ButtonContainer"; +import { ProviderSettings } from "./ProviderSettings"; import CustomAvatar from "./ui/CustomAvatar"; import CustomButton from "./ui/CustomButton"; import CustomCircularProgress from "./ui/CustomCircularProgress"; -import Input from "./ui/Input"; type Props = { hidePoints?: boolean; @@ -64,43 +59,24 @@ export default function ContractInfo({ const { maxWidth430 } = useBreakPoints(); const { openDialog } = useDialogs(); const { showSpinner, hideSpinner } = useSpinner(); + const [hasPendingUpdate, setPendingUpdateStatus] = useState(false); + const { showSnackbar } = useSnackbars(); + const router = useRouter(); - const [expiryMs, setExpiryMs] = useState( - providerInfo.stamp_validity_ms ?? null, + const [isProviderActive] = useState( + providerInfo.status === ProviderStatus.Active, ); - const expiryDays = - typeof expiryMs === "number" - ? Temporal.Duration.from({ milliseconds: expiryMs }).total("days") - : 0; - - const onExpiryChange = useCallback( - ({ target: { value } }: ChangeEvent) => - setExpiryMs( - typeof value === "string" - ? Temporal.Duration.from({ days: parseInt(value) }).total( - "milliseconds", - ) - : null, - ), - - [setExpiryMs], - ); + // const expiryDays = millisecondsToDays(providerInfo.stamp_validity_ms ?? null); const verifyHandler = useCallback(async () => { - if (isPreview) { - return; - } + if (!isPreview && providerInfo.is_user_a_human) { + showSpinner(); - showSpinner(); - // Call add_stamp method - if (providerInfo.is_user_a_human) { - try { - await contract.add_stamp(providerInfo.id); - } catch (error) { - console.error(error); - } - hideSpinner(); + contract + .add_stamp(providerInfo.id) + .catch(console.error) + .finally(hideSpinner); } }, [ isPreview, @@ -113,26 +89,15 @@ export default function ContractInfo({ const { saveProvider } = useProviderStatusChecker(); const getCheckHandler = useCallback(() => { - if (isPreview) { - return; - } - - // Save provider to check if the user is now a human there - saveProvider(providerInfo.id); + if (!isPreview) { + // Save provider to check if the user is now a human there + saveProvider(providerInfo.id); - // Open up the external URL - window.open(providerInfo.external_url, "_blank"); + // Open up the external URL + window.open(providerInfo.external_url, "_blank"); + } }, [isPreview, providerInfo.external_url, providerInfo.id, saveProvider]); - const [points, setPoints] = useState(providerInfo.default_weight); - const [previousPoints, setPreviousPoints] = useState( - providerInfo.default_weight, - ); - const debouncedPoints = useDebounce(points, 800); - const [updating, setUpdating] = useState(false); - const { showSnackbar } = useSnackbars(); - const router = useRouter(); - const imageURL = providerInfo.icon_url ? providerInfo.icon_url.replace( "https://gateway.pinata.cloud/ipfs/", @@ -140,60 +105,20 @@ export default function ContractInfo({ ) : null; - // Check if it's needed to update the Provider points - useEffect(() => { - if (isPreview) { - setPreviousPoints(debouncedPoints); - return; - } - - if (debouncedPoints !== previousPoints) { - // Update this Provider points - (async () => { - setUpdating(true); - await contract.update_provider({ - provider_id: providerInfo.id, - default_weight: debouncedPoints, - }); - setUpdating(false); - setPreviousPoints(debouncedPoints); - - // Update this provider withing store - updateProvider({ - provider_id: providerInfo.id, - default_weight: debouncedPoints, - }); - })(); - } - }, [ - debouncedPoints, - points, - previousPoints, - providerInfo.id, - isPreview, - updateProvider, - ]); - - const changePointsHandler = useCallback(async (newValue: number) => { - setPoints(newValue); - }, []); - - const [isProviderActive] = useState( - providerInfo.status === ProviderStatus.Active, - ); - // Switch Activation const switchActivation = useCallback(async () => { if (isPreview) { return; } - setUpdating(true); + setPendingUpdateStatus(true); + if (!isProviderActive) { await contract.admin_activate_provider({ provider_id: providerInfo.id, default_weight: providerInfo.default_weight || 0, }); + updateProvider({ provider_id: providerInfo.id, default_weight: providerInfo.default_weight || 0, @@ -211,12 +136,14 @@ export default function ContractInfo({ await contract.admin_deactivate_provider({ provider_id: providerInfo.id, }); + updateProvider({ provider_id: providerInfo.id, status: ProviderStatus.Deactivated, }); } - setUpdating(false); + + setPendingUpdateStatus(false); }, [ providerInfo.default_weight, providerInfo.id, @@ -285,7 +212,7 @@ export default function ContractInfo({ {!hidePoints && ( - {previousPoints} + {providerInfo.default_weight} Points @@ -341,90 +268,12 @@ export default function ContractInfo({ mt={2} mb={2} > - {providerInfo.description || - "Lorem ipsum dolor sit amet, consectet adipiscing elit, sed do eiusmod tempor ncididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."} + {providerInfo.description || "No description provided."} - {/* Edit Points */} {isAdmin && ( - - - - - Edit Points - - - - - - - {previousPoints} pts - - - - - {updating ? ( - - ) : ( - - changePointsHandler(newValue as number) - } - /> - )} - - - - } - label="Edit Expiry" - labelDecoration={ - - } - type="number" - integersOnly - min={0} - fontSize={20} - placeholder="30" - defaultValue={expiryDays} - rightComponent={ - - Days - - } - onChange={onExpiryChange} - /> - - - contract.update_provider({ - provider_id: providerInfo.id, - stamp_validity_ms: expiryMs, - }) - } - > - Save - - + )} @@ -485,7 +334,7 @@ export default function ContractInfo({ ) : ( <> - {updating ? ( + {hasPendingUpdate ? ( ) : ( <> diff --git a/components/ProviderSettings.tsx b/components/ProviderSettings.tsx new file mode 100644 index 0000000..148d21b --- /dev/null +++ b/components/ProviderSettings.tsx @@ -0,0 +1,163 @@ +import AutoDeleteOutlinedIcon from "@mui/icons-material/AutoDeleteOutlined"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; +import { Slider, Stack, Typography } from "@mui/material"; +import { Formik, FormikHelpers } from "formik"; +import { ChangeEvent, useCallback, useState } from "react"; + +import CustomButton from "@nadabot/components/ui/CustomButton"; +import { useProviders } from "@nadabot/hooks/store/useProviders"; +import * as contract from "@nadabot/services/contracts/sybil.nadabot"; +import { + ProviderExternalWithIsHuman, + UpdateProviderInput, +} from "@nadabot/services/contracts/sybil.nadabot/interfaces/providers"; +import colors from "@nadabot/theme/colors"; +import { daysToMilliseconds, millisecondsToDays } from "@nadabot/utils/time"; + +import Input from "./ui/Input"; + +export type ProviderSettingsProps = { + disabled?: boolean; + providerInfo: ProviderExternalWithIsHuman; +}; + +interface FormValues + extends Pick {} + +export const ProviderSettings = ({ + disabled = false, + providerInfo, +}: ProviderSettingsProps) => { + const { default_weight, stamp_validity_ms } = providerInfo; + const initialValues: FormValues = { default_weight, stamp_validity_ms }; + + const { updateProvider } = useProviders(); + + const [expiryMs, setExpiryMs] = useState( + providerInfo.stamp_validity_ms ?? null, + ); + + const expiryDays = millisecondsToDays(expiryMs); + + const onExpiryChange = useCallback( + ({ target: { value } }: ChangeEvent) => + setExpiryMs(daysToMilliseconds(parseInt(value))), + + [setExpiryMs], + ); + + const onSubmit = ( + values: FormValues, + { setSubmitting }: FormikHelpers, + ) => { + console.table(values); + + contract + .update_provider({ provider_id: providerInfo.id, ...values }) + .then((provider) => { + updateProvider({ provider_id: provider.id, ...provider }); + setSubmitting(false); + }) + .catch(console.error); + }; + + return ( +
+ + {({ + values, + errors, + // touched, + handleChange, + handleBlur, + handleSubmit, + isSubmitting, + }) => { + const isDisabled = disabled || isSubmitting; + + return ( +
+ + + + + Edit Points + + + + + + + {`${values.default_weight} pts`} + + + + + + + + + } + leftComponent={ + + } + type="number" + integersOnly + min={0} + fontSize={20} + placeholder="30" + defaultValue={expiryDays} + onChange={onExpiryChange} + rightComponent={ + + Days + + } + errorMessage={errors.stamp_validity_ms} + disabled={isDisabled} + /> + + + Save + + +
+ ); + }} +
+
+ ); +}; diff --git a/components/ui/CustomButton.tsx b/components/ui/CustomButton.tsx index 5917792..6489573 100644 --- a/components/ui/CustomButton.tsx +++ b/components/ui/CustomButton.tsx @@ -1,4 +1,4 @@ -import { Button, SxProps, Theme } from "@mui/material"; +import { Button, ButtonProps } from "@mui/material"; const fontSizes = { small: 14, @@ -26,16 +26,14 @@ const variants = { red: "contained", }; -type Props = { - children: any; +type Props = Pick< + ButtonProps, + "children" | "disabled" | "onMouseOut" | "onMouseOver" | "sx" | "type" +> & { onClick?: () => void; - onMouseOver?: () => void; - onMouseOut?: () => void; fontSize?: "small" | "medium" | "large"; bodySize?: "small" | "medium" | "large"; color?: "white" | "blue" | "beige" | "red"; - sx?: SxProps; - disabled?: boolean; }; export default function CustomButton(props: Props) { diff --git a/utils/time.ts b/utils/time.ts new file mode 100644 index 0000000..2ead5b2 --- /dev/null +++ b/utils/time.ts @@ -0,0 +1,11 @@ +import { Temporal } from "temporal-polyfill"; + +export const millisecondsToDays = (milliseconds: number | null) => + typeof milliseconds === "number" + ? Temporal.Duration.from({ milliseconds }).total("days") + : 0; + +export const daysToMilliseconds = (days: number | null) => + typeof days === "number" + ? Temporal.Duration.from({ days }).total("milliseconds") + : 0; From 1ceed00864458b2eac3f012cf24ee25d012f9d06 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Mon, 6 May 2024 15:19:32 +0400 Subject: [PATCH 09/81] Handle duration string parsing error --- utils/time.ts | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/utils/time.ts b/utils/time.ts index 2ead5b2..6ab557c 100644 --- a/utils/time.ts +++ b/utils/time.ts @@ -1,11 +1,33 @@ import { Temporal } from "temporal-polyfill"; -export const millisecondsToDays = (milliseconds: number | null) => - typeof milliseconds === "number" - ? Temporal.Duration.from({ milliseconds }).total("days") - : 0; - -export const daysToMilliseconds = (days: number | null) => - typeof days === "number" - ? Temporal.Duration.from({ days }).total("milliseconds") - : 0; +export const millisecondsToDays = ( + value: Temporal.DurationLike["milliseconds"] | string | null, +) => { + try { + return Temporal.Duration.from({ + milliseconds: typeof value === "string" ? parseInt(value) : value ?? 0, + }).total("days"); + } catch { + const error = new TypeError(`Unable to convert \`${value}\` to days`); + + console.error(error); + throw error; + } +}; + +export const daysToMilliseconds = ( + value: Temporal.DurationLike["days"] | string | null, +) => { + try { + return Temporal.Duration.from({ + days: typeof value === "string" ? parseInt(value) : value ?? 0, + }).total("milliseconds"); + } catch { + const error = new TypeError( + `Unable to convert \`${value}\` to milliseconds`, + ); + + console.error(error); + throw error; + } +}; From cb654f1c58c75b6c250298d46937c583f170b8a3 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Mon, 6 May 2024 15:20:08 +0400 Subject: [PATCH 10/81] fix: Pass missing `type` prop --- components/ui/CustomButton.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/ui/CustomButton.tsx b/components/ui/CustomButton.tsx index 6489573..c337b95 100644 --- a/components/ui/CustomButton.tsx +++ b/components/ui/CustomButton.tsx @@ -39,6 +39,7 @@ type Props = Pick< export default function CustomButton(props: Props) { return ( - )} - + + {providerInfo.is_user_a_human ? "Verify" : "Get Check"} + )} )} - +
); diff --git a/components/provider/adminSettingsForm.ts b/components/provider/adminSettingsForm.ts index d7f1d9a..576fc9a 100644 --- a/components/provider/adminSettingsForm.ts +++ b/components/provider/adminSettingsForm.ts @@ -16,13 +16,11 @@ interface ProviderAdminSettingsValues export type ProviderAdminSettingsFormParameters = { disabled?: boolean; providerInfo: ProviderExternal; - indicatePendingUpdate?: (isSubmitting: boolean) => void; }; export const useAdminSettingsForm = ({ disabled, providerInfo, - indicatePendingUpdate, }: ProviderAdminSettingsFormParameters) => { const { updateProvider } = useProviders(); @@ -43,8 +41,6 @@ export const useAdminSettingsForm = ({ resetForm, }: FormikHelpers, ) => { - indicatePendingUpdate?.(true); - contract .update_provider({ provider_id: providerInfo.id, @@ -65,7 +61,6 @@ export const useAdminSettingsForm = ({ }); setSubmitting(false); - indicatePendingUpdate?.(false); }) .catch(console.error); }, diff --git a/components/ui/CustomButton.tsx b/components/ui/CustomButton.tsx index c337b95..d5d9786 100644 --- a/components/ui/CustomButton.tsx +++ b/components/ui/CustomButton.tsx @@ -1,5 +1,7 @@ import { Button, ButtonProps } from "@mui/material"; +import CustomCircularProgress from "./CustomCircularProgress"; + const fontSizes = { small: 14, medium: 26, @@ -12,14 +14,18 @@ const bodySizes = { large: 68, }; -const colors = { - white: "primary", - blue: "primary", - beige: "warning", - red: "error", -}; +const colors: Record<"white" | "blue" | "beige" | "red", ButtonProps["color"]> = + { + white: "primary", + blue: "primary", + beige: "warning", + red: "error", + }; -const variants = { +const variants: Record< + "white" | "blue" | "beige" | "red", + ButtonProps["variant"] +> = { white: "text", blue: "contained", beige: "contained", @@ -30,29 +36,47 @@ type Props = Pick< ButtonProps, "children" | "disabled" | "onMouseOut" | "onMouseOver" | "sx" | "type" > & { - onClick?: () => void; - fontSize?: "small" | "medium" | "large"; bodySize?: "small" | "medium" | "large"; color?: "white" | "blue" | "beige" | "red"; + fontSize?: "small" | "medium" | "large"; + onClick?: () => void; + progress?: boolean; }; -export default function CustomButton(props: Props) { +export default function CustomButton({ + color = "white", + bodySize, + fontSize, + progress = false, + sx, + children, + ...props +}: Props) { return ( ); } From c69f8776bf570f70b96eb702e3d5696485d4ca6f Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Fri, 10 May 2024 16:36:15 +0400 Subject: [PATCH 18/81] Create Slider component --- components/ui/Slider.tsx | 98 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 components/ui/Slider.tsx diff --git a/components/ui/Slider.tsx b/components/ui/Slider.tsx new file mode 100644 index 0000000..356364e --- /dev/null +++ b/components/ui/Slider.tsx @@ -0,0 +1,98 @@ +import { + Slider as GenericSlider, + SliderProps as GenericSliderProps, + Stack, + Typography, +} from "@mui/material"; +import { useMemo } from "react"; + +import colors from "@nadabot/theme/colors"; + +export type SliderProps = Pick< + GenericSliderProps, + "disabled" | "min" | "max" | "name" | "onBlur" | "onChange" | "value" +> & { + label: string; + labelDecoration?: JSX.Element; + unitLabel?: string; +}; + +export const Slider: React.FC = ({ + disabled = false, + label, + labelDecoration, + unitLabel, + value, + ...props +}) => { + const displayValue = useMemo( + () => `${value} ${unitLabel ?? ""}`.trim(), + [unitLabel, value], + ); + + const inputProps = useMemo( + () => ({ disabled, value, ...props }), + [disabled, props, value], + ); + + return ( + + + + + {label} + + + {labelDecoration} + + + + + {displayValue} + + + + + + + ); +}; From ffb175a188957b0d66c4396f6e6f417c0a0d895b Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Fri, 10 May 2024 16:37:41 +0400 Subject: [PATCH 19/81] Update input component styles --- components/ui/Input.tsx | 13 ++++++++----- components/ui/RegularInput.tsx | 9 +++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/components/ui/Input.tsx b/components/ui/Input.tsx index 7f3e69d..da418d6 100644 --- a/components/ui/Input.tsx +++ b/components/ui/Input.tsx @@ -18,13 +18,13 @@ type Props = Pick< | "name" | "onChange" | "leftComponent" + | "placeholder" | "rightComponent" | "sx" | "type" > & { label?: string; labelDecoration?: JSX.Element; - placeholder: string; info?: string; optional?: boolean; errorMessage?: string; @@ -53,12 +53,14 @@ export default function Input({ }: Props) { return ( - + {label && ( {label} @@ -67,9 +69,10 @@ export default function Input({ {optional && ( (optional) diff --git a/components/ui/RegularInput.tsx b/components/ui/RegularInput.tsx index d850447..aaf47d7 100644 --- a/components/ui/RegularInput.tsx +++ b/components/ui/RegularInput.tsx @@ -1,5 +1,5 @@ import { Box, Stack, SxProps, Theme } from "@mui/material"; -import { ChangeEvent, HTMLInputTypeAttribute } from "react"; +import { ChangeEvent, HTMLInputTypeAttribute, useMemo } from "react"; import colors from "@nadabot/theme/colors"; @@ -42,6 +42,11 @@ const RegularInput = ({ min, max, }: RegularInputProps) => { + const background = useMemo( + () => (disabled ? colors.NEUTRAL100 : colors.WHITE), + [disabled], + ); + return ( Date: Fri, 10 May 2024 16:38:03 +0400 Subject: [PATCH 20/81] Update color palette --- theme/colors.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/theme/colors.ts b/theme/colors.ts index 3f0910a..06fcf40 100644 --- a/theme/colors.ts +++ b/theme/colors.ts @@ -37,6 +37,7 @@ const colors = { NEUTRAL400: "#A6A6A6", NEUTRAL500: "#7B7B7B", NEUTRAL700: "#525252", + NEUTRAL950: "#292929", ERROR_RED: "#DD3345", ERROR_RED_LIGHT: "#FCEFF0", From cbf3ac0edcd1080b33d78fc5d390a64802de74ec Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Fri, 10 May 2024 16:39:08 +0400 Subject: [PATCH 21/81] wip: Update provider card and admin settings UI --- components/provider/ProviderAdminSettings.tsx | 172 ++++++++++-------- components/provider/ProviderCard.tsx | 47 +++-- components/provider/adminSettingsForm.ts | 24 ++- 3 files changed, 144 insertions(+), 99 deletions(-) diff --git a/components/provider/ProviderAdminSettings.tsx b/components/provider/ProviderAdminSettings.tsx index e138721..13321f9 100644 --- a/components/provider/ProviderAdminSettings.tsx +++ b/components/provider/ProviderAdminSettings.tsx @@ -1,11 +1,12 @@ import AutoDeleteOutlinedIcon from "@mui/icons-material/AutoDeleteOutlined"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import SettingsOutlinedIcon from "@mui/icons-material/SettingsOutlined"; -import { Slider, Stack, SxProps, Theme, Typography } from "@mui/material"; -import { useMemo } from "react"; +import { Stack, Switch, SxProps, Theme, Typography } from "@mui/material"; +import { useEffect, useMemo } from "react"; import CustomButton from "@nadabot/components/ui/CustomButton"; import Input from "@nadabot/components/ui/Input"; +import { Slider } from "@nadabot/components/ui/Slider"; import colors from "@nadabot/theme/colors"; import { @@ -18,34 +19,46 @@ export type ProviderAdminSettingsProps = Pick< "disabled" | "providerInfo" > & { embedded?: boolean; + indicateUnsavedChanges?: (hasUnsavedChanges: boolean) => void; sx?: SxProps; }; export const ProviderAdminSettings = ({ embedded = false, disabled = false, + indicateUnsavedChanges, providerInfo, sx, }: ProviderAdminSettingsProps) => { const { errors, - isDisabled, - isLocked, - isSubmitting, handleBlur, handleChange, handleSubmit, handleReset, + hasChanges, + isExpiryEnabled, + isDisabled, + isLocked, + isSubmitting, + onExpirySwitch, values, } = useAdminSettingsForm({ disabled, providerInfo }); + useEffect( + () => void indicateUnsavedChanges?.(hasChanges), + [hasChanges, indicateUnsavedChanges], + ); + const actions = useMemo( () => ( - - - - - - Admin Settings - - - - - - - - Edit Points - - - + + + + - - - {`${values.default_weight} pts`} - - - + + Admin Settings + + + + } min={1} max={100} - valueLabelDisplay="auto" value={values.default_weight} + unitLabel="pts" onBlur={handleBlur} onChange={handleChange} disabled={isLocked} /> - - - } - leftComponent={ - - } - type="number" - integersOnly - min={0} - fontSize={20} - placeholder="30" - defaultValue={values.stamp_validity_days} - onChange={handleChange} - rightComponent={ - - Days - - } - errorMessage={errors.stamp_validity_days} - disabled={isLocked} - /> + + + + + + } + leftComponent={ + + } + type="number" + integersOnly + min={0} + fontSize={20} + defaultValue={values.stamp_validity_days} + onChange={handleChange} + rightComponent={ + + Days + + } + errorMessage={errors.stamp_validity_days} + disabled={isLocked || !isExpiryEnabled} + /> - {!embedded && actions} + {embedded && ( + {}} + > + View full settings + + )} + - {embedded && actions} + {actions} ); }; diff --git a/components/provider/ProviderCard.tsx b/components/provider/ProviderCard.tsx index 26c1f13..102a21a 100644 --- a/components/provider/ProviderCard.tsx +++ b/components/provider/ProviderCard.tsx @@ -52,6 +52,7 @@ export const ProviderCard: React.FC = ({ const { maxWidth430 } = useBreakPoints(); const { openDialog } = useDialogs(); const { showSpinner, hideSpinner } = useSpinner(); + const [isFooterHidden, setIsFooterHidden] = useState(false); const [hasPendingUpdate, indicatePendingUpdate] = useState(false); const { showSnackbar } = useSnackbars(); const router = useRouter(); @@ -205,21 +206,18 @@ export const ProviderCard: React.FC = ({ return ( - + {/* Circle */} @@ -303,27 +301,26 @@ export const ProviderCard: React.FC = ({ {providerInfo.description || "No description provided."} - - {isAdmin && ( - - )} + {isAdmin && ( + + )} + {/* Footer */} {authorCredentials} @@ -362,6 +359,7 @@ export const ProviderCard: React.FC = ({ color="beige" bodySize="medium" onClick={switchActivation} + progress={hasPendingUpdate} sx={{ mt: maxWidth430 ? 2 : 0, px: 2, @@ -400,7 +398,6 @@ export const ProviderCard: React.FC = ({ : getCheckHandler } sx={{ mt: maxWidth430 ? 2 : 0, ...verifyButtonSx }} - progress={hasPendingUpdate} > {providerInfo.is_user_a_human ? "Verify" : "Get Check"} diff --git a/components/provider/adminSettingsForm.ts b/components/provider/adminSettingsForm.ts index 576fc9a..28b984c 100644 --- a/components/provider/adminSettingsForm.ts +++ b/components/provider/adminSettingsForm.ts @@ -1,4 +1,5 @@ import { FormikHelpers, useFormik } from "formik"; +import { useCallback, useState } from "react"; import { useProviders } from "@nadabot/hooks/store/useProviders"; import * as contract from "@nadabot/services/contracts/sybil.nadabot"; @@ -22,8 +23,20 @@ export const useAdminSettingsForm = ({ disabled, providerInfo, }: ProviderAdminSettingsFormParameters) => { + const isStampValiditySet = providerInfo.stamp_validity_ms !== null; const { updateProvider } = useProviders(); + const [isExpiryEnabled, setIsExpiryEnabled] = + useState(isStampValiditySet); + + const onExpirySwitch: ( + event: React.ChangeEvent, + checked: boolean, + ) => void = useCallback( + (_, enabled) => setIsExpiryEnabled(enabled), + [setIsExpiryEnabled], + ); + const { dirty, isSubmitting, isValid, ...form } = useFormik({ initialValues: { @@ -45,7 +58,10 @@ export const useAdminSettingsForm = ({ .update_provider({ provider_id: providerInfo.id, default_weight, - stamp_validity_ms: daysToMilliseconds(stamp_validity_days), + + stamp_validity_ms: isExpiryEnabled + ? daysToMilliseconds(stamp_validity_days) + : null, }) .then(({ id: provider_id, ...updated }) => { updateProvider({ provider_id, ...updated }); @@ -66,12 +82,16 @@ export const useAdminSettingsForm = ({ }, }); + const hasChanges = dirty || isExpiryEnabled !== isStampValiditySet; const isLocked = disabled || isSubmitting; return { ...form, + hasChanges, isLocked, - isDisabled: isLocked || !dirty || !isValid, + isDisabled: isLocked || !hasChanges || !isValid, + isExpiryEnabled, isSubmitting, + onExpirySwitch, }; }; From 8d6ae52cd38cd3c2a2266a001a02ccabf7c4f084 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Sun, 12 May 2024 21:08:44 +0400 Subject: [PATCH 22/81] wip: Extract stamp editor component & Create stamp edit page --- .vscode/settings.json | 4 +- .../{pages => }/admin/AdminChecksSection.tsx | 4 +- .../admin/AdminDashboardSection.tsx | 0 .../admin/AdminReviewChecksSection.tsx | 10 +- components/containers/ContractsContainer.tsx | 6 +- .../components/Description.tsx | 9 +- .../components/NextProviders.tsx | 4 +- components/{pages => }/home/ChecksSection.tsx | 6 +- .../{pages => }/home/CompletedSection.tsx | 6 +- .../components/MyHumanityScore.tsx | 0 .../components/RecentCheckItem.tsx | 0 .../components/RecentChecks.tsx | 0 .../{pages => }/home/ExploreSection/index.tsx | 2 +- .../home/InvitationHeroSection.tsx | 0 .../home/PendingVerificationSection.tsx | 6 +- .../StampAdminSettings.tsx} | 41 +- .../ProviderCard.tsx => stamp/StampCard.tsx} | 8 +- components/stamp/StampEditor.tsx | 228 ++++++++++ .../{provider => stamp}/adminSettingsForm.ts | 91 ++-- components/stamp/layout.tsx | 55 +++ components/stamp/settingsForm.ts | 244 ++++++++++ constants.ts | 1 + pages/account-info/index.tsx | 4 +- pages/add-stamp/index.tsx | 420 ------------------ pages/admin/index.tsx | 8 +- pages/index.tsx | 10 +- pages/stamp/create.tsx | 10 + pages/stamp/edit/[id].tsx | 18 + routes.ts | 2 +- services/contracts/sybil.nadabot/index.ts | 11 + .../sybil.nadabot/interfaces/providers.ts | 9 +- 31 files changed, 695 insertions(+), 522 deletions(-) rename components/{pages => }/admin/AdminChecksSection.tsx (84%) rename components/{pages => }/admin/AdminDashboardSection.tsx (100%) rename components/{pages => }/admin/AdminReviewChecksSection.tsx (95%) rename components/{pages => }/home/ChecksSection.tsx (92%) rename components/{pages => }/home/CompletedSection.tsx (90%) rename components/{pages => }/home/ExploreSection/components/MyHumanityScore.tsx (100%) rename components/{pages => }/home/ExploreSection/components/RecentCheckItem.tsx (100%) rename components/{pages => }/home/ExploreSection/components/RecentChecks.tsx (100%) rename components/{pages => }/home/ExploreSection/index.tsx (95%) rename components/{pages => }/home/InvitationHeroSection.tsx (100%) rename components/{pages => }/home/PendingVerificationSection.tsx (94%) rename components/{provider/ProviderAdminSettings.tsx => stamp/StampAdminSettings.tsx} (84%) rename components/{provider/ProviderCard.tsx => stamp/StampCard.tsx} (98%) create mode 100644 components/stamp/StampEditor.tsx rename components/{provider => stamp}/adminSettingsForm.ts (50%) create mode 100644 components/stamp/layout.tsx create mode 100644 components/stamp/settingsForm.ts delete mode 100644 pages/add-stamp/index.tsx create mode 100644 pages/stamp/create.tsx create mode 100644 pages/stamp/edit/[id].tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 5595e30..71fa8f4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,8 @@ "source.fixAll": "explicit" }, "cSpell.words": [ - "nadabot" + "nadabot", + "naxios", + "SHADOWGRAY" ] } \ No newline at end of file diff --git a/components/pages/admin/AdminChecksSection.tsx b/components/admin/AdminChecksSection.tsx similarity index 84% rename from components/pages/admin/AdminChecksSection.tsx rename to components/admin/AdminChecksSection.tsx index 90692be..dc7752a 100644 --- a/components/pages/admin/AdminChecksSection.tsx +++ b/components/admin/AdminChecksSection.tsx @@ -2,8 +2,8 @@ import { Stack, Typography } from "@mui/material"; import useBreakPoints from "@nadabot/hooks/useBreakPoints"; -import ContractsContainer from "../../containers/ContractsContainer"; -import { ShadowContainer } from "../../containers/ShadowContainer"; +import ContractsContainer from "../containers/ContractsContainer"; +import { ShadowContainer } from "../containers/ShadowContainer"; export default function AdminChecksSection() { const { maxWidth805 } = useBreakPoints(); diff --git a/components/pages/admin/AdminDashboardSection.tsx b/components/admin/AdminDashboardSection.tsx similarity index 100% rename from components/pages/admin/AdminDashboardSection.tsx rename to components/admin/AdminDashboardSection.tsx diff --git a/components/pages/admin/AdminReviewChecksSection.tsx b/components/admin/AdminReviewChecksSection.tsx similarity index 95% rename from components/pages/admin/AdminReviewChecksSection.tsx rename to components/admin/AdminReviewChecksSection.tsx index 5d7c21c..a3f2f16 100644 --- a/components/pages/admin/AdminReviewChecksSection.tsx +++ b/components/admin/AdminReviewChecksSection.tsx @@ -4,7 +4,7 @@ import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; import GridContainer from "@nadabot/components/containers/GridContainer"; -import { ProviderCard } from "@nadabot/components/provider/ProviderCard"; +import { StampCard } from "@nadabot/components/stamp/StampCard"; import CustomButton from "@nadabot/components/ui/CustomButton"; import FilterButton from "@nadabot/components/ui/FilterButton"; import RegularInput from "@nadabot/components/ui/RegularInput"; @@ -15,7 +15,7 @@ import { ProviderExternalWithIsHuman } from "@nadabot/services/contracts/sybil.n import colors from "@nadabot/theme/colors"; import { SearchIconA } from "@nadabot/theme/icons"; -import { ShadowContainer } from "../../containers/ShadowContainer"; +import { ShadowContainer } from "../containers/ShadowContainer"; type FilterType = "all" | "active" | "deactivated"; @@ -163,11 +163,7 @@ export default function AdminReviewChecksSection() { }} > {filteredProviders.map((provider) => ( - + ))}
)} diff --git a/components/containers/ContractsContainer.tsx b/components/containers/ContractsContainer.tsx index d37f83f..0ef354e 100644 --- a/components/containers/ContractsContainer.tsx +++ b/components/containers/ContractsContainer.tsx @@ -2,7 +2,7 @@ import { Stack, Typography } from "@mui/material"; import Fuse from "fuse.js"; import { useEffect, useState } from "react"; -import { ProviderCard } from "@nadabot/components/provider/ProviderCard"; +import { StampCard } from "@nadabot/components/stamp/StampCard"; import CustomCircularProgress from "@nadabot/components/ui/CustomCircularProgress"; import useFilteredProviders from "@nadabot/hooks/useFilteredProviders"; import { ProviderExternalWithIsHuman } from "@nadabot/services/contracts/sybil.nadabot/interfaces/providers"; @@ -102,7 +102,7 @@ export default function ContractsContainer({ overflow="scroll" > {filteredProviders.map((provider) => ( - {filteredProviders.map((provider) => ( - {/* Left */} @@ -85,7 +86,11 @@ export default function Description({ providerInfo }: Props) { {isAdmin && router.route === Routes.ADMIN_HOME && providerInfo !== undefined && ( - + )} ); diff --git a/components/dialogs/ViewProviderDialog/components/NextProviders.tsx b/components/dialogs/ViewProviderDialog/components/NextProviders.tsx index b8d6c4e..0017256 100644 --- a/components/dialogs/ViewProviderDialog/components/NextProviders.tsx +++ b/components/dialogs/ViewProviderDialog/components/NextProviders.tsx @@ -1,6 +1,6 @@ import { Stack, Typography } from "@mui/material"; -import { ProviderCard } from "@nadabot/components/provider/ProviderCard"; +import { StampCard } from "@nadabot/components/stamp/StampCard"; import { useUser } from "@nadabot/hooks/store/useUser"; import useBreakPoints from "@nadabot/hooks/useBreakPoints"; import useFilteredProviders from "@nadabot/hooks/useFilteredProviders"; @@ -38,7 +38,7 @@ export default function NextProviders({ providerInfo }: Props) { flexWrap="wrap" > {providers.slice(0, 3).map((provider) => ( - = 3}> {stamps.map((stamp) => ( - ( = 3}> {activeIsHuman.map((provider) => ( - & { embedded?: boolean; + heading?: string; indicateUnsavedChanges?: (hasUnsavedChanges: boolean) => void; sx?: SxProps; }; -export const ProviderAdminSettings = ({ +export const StampAdminSettings = ({ embedded = false, disabled = false, + heading = "Admin Settings", indicateUnsavedChanges, providerInfo, + onSubmit, sx, -}: ProviderAdminSettingsProps) => { +}: StampAdminSettingsProps) => { + const router = useRouter(); + const isStampPage = router.pathname.startsWith("/stamp/"); + const { errors, handleBlur, @@ -43,7 +51,7 @@ export const ProviderAdminSettings = ({ isSubmitting, onExpirySwitch, values, - } = useAdminSettingsForm({ disabled, providerInfo }); + } = useAdminSettingsForm({ disabled, providerInfo, onSubmit }); useEffect( () => void indicateUnsavedChanges?.(hasChanges), @@ -106,7 +114,7 @@ export const ProviderAdminSettings = ({ - Admin Settings + {heading}
@@ -172,16 +180,19 @@ export const ProviderAdminSettings = ({ disabled={isLocked || !isExpiryEnabled} /> - {embedded && ( - {}} + style={{ + width: "fit-content", + textDecoration: "underline", + }} > View full settings - + )}
diff --git a/components/provider/ProviderCard.tsx b/components/stamp/StampCard.tsx similarity index 98% rename from components/provider/ProviderCard.tsx rename to components/stamp/StampCard.tsx index 102a21a..687ea31 100644 --- a/components/provider/ProviderCard.tsx +++ b/components/stamp/StampCard.tsx @@ -24,9 +24,9 @@ import colors from "@nadabot/theme/colors"; import removeViewStampFromURLQuery from "@nadabot/utils/removeViewStampFromURLQuery"; import { daysSinceTimestamp, millisecondsToDays } from "@nadabot/utils/time"; -import { ProviderAdminSettings } from "./ProviderAdminSettings"; +import { StampAdminSettings } from "./StampAdminSettings"; -export type ProviderCardProps = { +export type StampCardProps = { hidePoints?: boolean; sx?: SxProps; colorSystem?: "regular" | "admin"; @@ -37,7 +37,7 @@ export type ProviderCardProps = { adminView?: boolean; }; -export const ProviderCard: React.FC = ({ +export const StampCard: React.FC = ({ hidePoints, sx, providerInfo, @@ -304,7 +304,7 @@ export const ProviderCard: React.FC = ({ {isAdmin && ( - = ({ id }) => { + const { isAdmin } = useUser(); + const router = useRouter(); + + const { maxWidth1200, maxWidth962, maxWidth600, maxWidth430 } = + useBreakPoints(); + + const { + errors, + handleChange, + handleSubmit, + isSubmitting, + onImagePickerClick, + values, + } = useSettingsForm({ id }); + + const preview = ( + + ); + + return ( + + {/* Stamp Inputs and Preview */} + + {/* Inputs */} + + + + + + + + + + + + + + + + + + + {/* Preview */} + + + + Stamp Card Preview + + + + + + + This is the preview of your submitted Stamp + + + {preview} + + + + {isAdmin && ( + {}} + /> + )} + + + + Go Back + + + + Final Submit + + + + ); +}; diff --git a/components/provider/adminSettingsForm.ts b/components/stamp/adminSettingsForm.ts similarity index 50% rename from components/provider/adminSettingsForm.ts rename to components/stamp/adminSettingsForm.ts index 28b984c..9786292 100644 --- a/components/provider/adminSettingsForm.ts +++ b/components/stamp/adminSettingsForm.ts @@ -2,27 +2,35 @@ import { FormikHelpers, useFormik } from "formik"; import { useCallback, useState } from "react"; import { useProviders } from "@nadabot/hooks/store/useProviders"; -import * as contract from "@nadabot/services/contracts/sybil.nadabot"; +import * as sybilContract from "@nadabot/services/contracts/sybil.nadabot"; import { ProviderExternal, UpdateProviderInput, } from "@nadabot/services/contracts/sybil.nadabot/interfaces/providers"; import { daysToMilliseconds, millisecondsToDays } from "@nadabot/utils/time"; -interface ProviderAdminSettingsValues - extends Pick { +export type StampAdminSettingsValues = Pick< + UpdateProviderInput, + "default_weight" +> & { stamp_validity_days: number; -} +}; -export type ProviderAdminSettingsFormParameters = { +export type StampAdminSettingsFormParameters = { disabled?: boolean; providerInfo: ProviderExternal; + + onSubmit?: ( + values: StampAdminSettingsValues, + actions?: FormikHelpers, + ) => void; }; export const useAdminSettingsForm = ({ disabled, providerInfo, -}: ProviderAdminSettingsFormParameters) => { + onSubmit: customSubmitHandler, +}: StampAdminSettingsFormParameters) => { const isStampValiditySet = providerInfo.stamp_validity_ms !== null; const { updateProvider } = useProviders(); @@ -37,8 +45,43 @@ export const useAdminSettingsForm = ({ [setIsExpiryEnabled], ); + const submitHandler = useCallback( + ( + { default_weight, stamp_validity_days }: StampAdminSettingsValues, + { setSubmitting, resetForm }: FormikHelpers, + ) => { + sybilContract + .update_provider({ + provider_id: providerInfo.id, + default_weight, + + stamp_validity_ms: isExpiryEnabled + ? daysToMilliseconds(stamp_validity_days) + : null, + }) + .then(({ id: provider_id, ...updated }) => { + updateProvider({ provider_id, ...updated }); + + resetForm({ + values: { + default_weight: updated.default_weight, + + stamp_validity_days: millisecondsToDays( + updated.stamp_validity_ms ?? 0, + ), + }, + }); + + setSubmitting(false); + }) + .catch(console.error); + }, + + [isExpiryEnabled, providerInfo.id, updateProvider], + ); + const { dirty, isSubmitting, isValid, ...form } = - useFormik({ + useFormik({ initialValues: { default_weight: providerInfo.default_weight, @@ -47,39 +90,7 @@ export const useAdminSettingsForm = ({ ), }, - onSubmit: ( - { default_weight, stamp_validity_days }: ProviderAdminSettingsValues, - { - setSubmitting, - resetForm, - }: FormikHelpers, - ) => { - contract - .update_provider({ - provider_id: providerInfo.id, - default_weight, - - stamp_validity_ms: isExpiryEnabled - ? daysToMilliseconds(stamp_validity_days) - : null, - }) - .then(({ id: provider_id, ...updated }) => { - updateProvider({ provider_id, ...updated }); - - resetForm({ - values: { - default_weight: updated.default_weight, - - stamp_validity_days: millisecondsToDays( - updated.stamp_validity_ms ?? 0, - ), - }, - }); - - setSubmitting(false); - }) - .catch(console.error); - }, + onSubmit: customSubmitHandler ?? submitHandler, }); const hasChanges = dirty || isExpiryEnabled !== isStampValiditySet; diff --git a/components/stamp/layout.tsx b/components/stamp/layout.tsx new file mode 100644 index 0000000..a717a4d --- /dev/null +++ b/components/stamp/layout.tsx @@ -0,0 +1,55 @@ +import { Container, Stack, Tooltip, Typography } from "@mui/material"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import React, { useEffect } from "react"; + +import ProtectedPage from "@nadabot/components/auth/ProtectedPage"; +import { DIALOGS } from "@nadabot/contexts/DialogsProvider"; +import useDialogs from "@nadabot/hooks/useDialogs"; +import useTransactionDetection from "@nadabot/hooks/useTransactionDetection"; +import colors from "@nadabot/theme/colors"; + +export default function StampPageLayout({ + children, +}: { + children: React.ReactNode; +}) { + const router = useRouter(); + const { openDialog } = useDialogs(); + // Check if there was an transaction, if so, show the "Done" Dialog + const transactionCompleted = useTransactionDetection(); + + useEffect(() => { + if (transactionCompleted) { + openDialog({ dialog: DIALOGS.StampSent }); + } + }, [transactionCompleted, openDialog]); + + return ( + + + + See the Docs here + + } + > + + + {`${typeof router.query.id === "string" ? "Edit" : "Add"} Stamp/Check`} + + + + A stamp is a smart-contract enabled check on NEAR to be added to + the nadabot sybil registry, to verify something about an account + whether it be a role, identity, proof of ownership, or behavior. + + + + + {children} + + + ); +} diff --git a/components/stamp/settingsForm.ts b/components/stamp/settingsForm.ts new file mode 100644 index 0000000..4fdb789 --- /dev/null +++ b/components/stamp/settingsForm.ts @@ -0,0 +1,244 @@ +import { useFormik } from "formik"; +import { useCallback, useEffect } from "react"; +import { useFilePicker } from "use-file-picker"; +import { + FileAmountLimitValidator, + FileSizeValidator, + FileTypeValidator, +} from "use-file-picker/validators"; +import { object, string } from "yup"; + +import { + DEFAULT_ACCOUNT_ID_ARG_NAME, + MAX_GAS, + MAX_PROVIDER_DESCRIPTION_LENGTH, + MAX_PROVIDER_EXTERNAL_URL_LENGTH, + MAX_PROVIDER_NAME_LENGTH, +} from "@nadabot/constants"; +import { DIALOGS } from "@nadabot/contexts/DialogsProvider"; +import useDialogs from "@nadabot/hooks/useDialogs"; +import useSpinner from "@nadabot/hooks/useSpinner"; +import { naxiosInstance } from "@nadabot/services/contracts"; +import * as sybilContract from "@nadabot/services/contracts/sybil.nadabot"; +import { ProviderExternal } from "@nadabot/services/contracts/sybil.nadabot/interfaces/providers"; +import * as pinataServices from "@nadabot/services/pinata"; + +const formSchema = object().shape({ + imageURL: string() + .min(4, "You should attach an image") + .required("Attach an image"), + + title: string() + .min(4, "Insert a valid title") + .max( + MAX_PROVIDER_NAME_LENGTH, + `Title shouldn't exceed ${MAX_PROVIDER_NAME_LENGTH} characters`, + ) + .required("Insert a valid title"), + + description: string() + .min(4, "Insert a valid description") + .max( + MAX_PROVIDER_DESCRIPTION_LENGTH, + `Description shouldn't exceed ${MAX_PROVIDER_DESCRIPTION_LENGTH} characters`, + ) + .required("Insert a valid description"), + + contractName: string() + .min(4, "Insert a valid contract address name") + .required("Insert a valid contract name"), + + method: string() + .min(3, "Insert a valid method") + .required("Insert a valid method"), + + externalLink: string() + .min(4, "Insert a valid external link") + .max( + MAX_PROVIDER_EXTERNAL_URL_LENGTH, + `Link shouldn't exceed ${MAX_PROVIDER_EXTERNAL_URL_LENGTH} characters`, + ) + .required("Insert a valid external link"), +}); + +export type StampSettingsFormParameters = { + id?: ProviderExternal["id"]; +}; + +export const useSettingsForm = ({ id }: StampSettingsFormParameters) => { + const isNew = typeof id !== "string"; + const { showSpinner, hideSpinner } = useSpinner(); + const { openDialog } = useDialogs(); + + const { openFilePicker: onImagePickerClick, filesContent } = useFilePicker({ + readAs: "DataURL", + accept: "image/*", + multiple: false, + + validators: [ + new FileAmountLimitValidator({ max: 1 }), + new FileTypeValidator(["jpg", "png", "svg"]), + new FileSizeValidator({ maxFileSize: 4 * 1024 * 1024 /* 4 MB */ }), + ], + }); + + const imagePickerValue = filesContent.at(0)?.content; + + const { + handleChange: onValueChange, + isSubmitting, + resetForm, + setFieldValue, + ...form + } = useFormik({ + validateOnChange: false, + validationSchema: formSchema, + + initialValues: { + imageURL: "", + title: "", + description: "", + contractName: "", + method: "", + externalLink: "", + + accountIdArgName: DEFAULT_ACCOUNT_ID_ARG_NAME, + gas: 0, + }, + + onSubmit: async ( + { + title, + description, + contractName, + method, + accountIdArgName, + externalLink, + gas, + }, + + { setFieldError }, + ) => { + showSpinner(); + + const contract = await naxiosInstance.contractApi({ + contractId: contractName, + }); + + // 1 - Check contract and method: The method must have a `account_id` parameter + try { + const response = await contract.view(method, { + // e.g.: args: {account_id: "no.account.near"} + args: { + [accountIdArgName || DEFAULT_ACCOUNT_ID_ARG_NAME]: + "no.account.near", + }, + }); + + // 1.1 validate if the response is a boolean + // NOTE: this returns an array of validated humans [should break]: registry-v2.i-am-human.testnet + // NOTE: this returns an boolean [should work]: + if (typeof response !== "boolean") { + setFieldError( + "method", + `The return is not a boolean, it is a${Array.isArray(response) ? "n array" : ` ${typeof response}`}!`, + ); + + hideSpinner(); + return; + } + } catch (error) { + // 1.2 validate the `accountIdArgName` parameter or other kind of contract error + setFieldError( + "method", + `The contract/method does not exist or does not have an "${accountIdArgName || DEFAULT_ACCOUNT_ID_ARG_NAME}" parameter.`, + ); + + hideSpinner(); + return; + } + + // 2 - Upload the image + // Upload image and get its CID + const iconImageCID = await pinataServices.uploadFile( + filesContent[0].content, + ); + + if (!iconImageCID) { + // Validate image upload + setFieldError( + "imageURL", + "There was an issue while trying to upload the image!", + ); + + hideSpinner(); + return; + } + + const validatedGas = gas && gas > MAX_GAS ? MAX_GAS : gas; + // Convert to indivisible gas units + // multiplying Tgas units by 10^12 + const providerGas = + validatedGas > 0 ? validatedGas * 10 ** 12 : undefined; + + // 3 - Register Stamp/Check + sybilContract + .register_provider({ + contract_id: contractName, + method_name: method, + account_id_arg_name: accountIdArgName || DEFAULT_ACCOUNT_ID_ARG_NAME, + provider_name: title, + description, + gas: providerGas, + icon_url: pinataServices.buildFileURL(iconImageCID), + external_url: externalLink, + }) + .then(() => { + hideSpinner(); + + // 4 - Show DONE Dialog -> this is going to take user to HOME page + openDialog({ dialog: DIALOGS.StampSent }); + }) + .catch((error) => { + hideSpinner(); + + const errorObj = JSON.parse(error.message); + const errorMsg = errorObj.kind.ExecutionError as string; + + openDialog({ + dialog: DIALOGS.Error, + + props: { + title: "Error", + description: errorMsg, + }, + }); + }); + }, + }); + + const handleChange = useCallback( + (event: React.ChangeEvent) => { + onValueChange(event); + form.setErrors({ ...form.errors, [event.target.name]: "" }); + }, + + [form, onValueChange], + ); + + useEffect(() => { + if (typeof imagePickerValue === "string") { + setFieldValue("imageURL", imagePickerValue); + } + }, [filesContent, setFieldValue, imagePickerValue]); + + useEffect(() => { + if (!isNew) { + sybilContract.get_provider({ provider_id: id }).then((provider) => { + if (provider !== undefined) resetForm(provider); + }); + } + }, [id, isNew, resetForm]); + + return { ...form, handleChange, isSubmitting, onImagePickerClick }; +}; diff --git a/constants.ts b/constants.ts index bbb557b..01edf15 100644 --- a/constants.ts +++ b/constants.ts @@ -44,3 +44,4 @@ export const MAX_PROVIDER_DESCRIPTION_LENGTH = 256; export const MAX_PROVIDER_EXTERNAL_URL_LENGTH = 256; export const MAX_PROVIDER_ICON_URL_LENGTH = 256; export const MAX_GAS = 100; //100_000_000_000_000; +export const DEFAULT_ACCOUNT_ID_ARG_NAME = "account_id"; diff --git a/pages/account-info/index.tsx b/pages/account-info/index.tsx index ab769ca..f927eb6 100644 --- a/pages/account-info/index.tsx +++ b/pages/account-info/index.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; import GridContainer from "@nadabot/components/containers/GridContainer"; -import { ProviderCard } from "@nadabot/components/provider/ProviderCard"; +import { StampCard } from "@nadabot/components/stamp/StampCard"; import CustomAvatar from "@nadabot/components/ui/CustomAvatar"; import CustomCircularProgress from "@nadabot/components/ui/CustomCircularProgress"; import Tag from "@nadabot/components/ui/Tag"; @@ -64,7 +64,7 @@ export default function AccountInfoPage() { const StampCards = useCallback( () => userStamps.map((stamp) => ( - { - if (transactionCompleted) { - openDialog({ dialog: DIALOGS.StampSent }); - } - }, [transactionCompleted, openDialog]); - - const formik = useFormik({ - validateOnChange: false, - initialValues: { - imageURL: "", - title: "", - description: "", - contractName: "", - method: "", - accountIdArgName: DEFAULT_ARG_NAME, - externalLink: "", - gas: 0, - }, - validationSchema: formSchema, - onSubmit: async ({ - title, - description, - contractName, - method, - accountIdArgName, - externalLink, - gas, - }) => { - showSpinner(); - - const contract = await naxiosInstance.contractApi({ - contractId: contractName, - }); - - // 1 - Check contract and method: The method must have a `account_id` parameter - try { - const response = await contract.view(method, { - // e.g.: args: {account_id: "no.account.near"} - args: { [accountIdArgName || DEFAULT_ARG_NAME]: "no.account.near" }, - }); - - // 1.1 validate if the response is a boolean - // NOTE: this returns an array of validated humans [should break]: registry-v2.i-am-human.testnet - // NOTE: this returns an boolean [should work]: - if (typeof response !== "boolean") { - formik.setFieldError( - "method", - `The return is not a boolean, it is a${Array.isArray(response) ? "n array" : ` ${typeof response}`}!`, - ); - hideSpinner(); - return; - } - } catch (error) { - // 1.2 validate the `accountIdArgName` parameter or other kind of contract error - formik.setFieldError( - "method", - `The contract/method does not exist or does not have an "${accountIdArgName || DEFAULT_ARG_NAME}" parameter.`, - ); - hideSpinner(); - return; - } - - // 2 - Upload the image - // Upload image and get its CID - const iconImageCID = await pinataServices.uploadFile( - filesContent[0].content, - ); - if (!iconImageCID) { - // Validate image upload - formik.setFieldError( - "imageURL", - "There was an issue while trying to upload the image!", - ); - hideSpinner(); - return; - } - - const validatedGas = gas && gas > MAX_GAS ? MAX_GAS : gas; - // Convert to indivisable gas units - // multiplying Tgas units by 10^12 - const providerGas = - validatedGas > 0 ? validatedGas * 10 ** 12 : undefined; - - // 3 - Register Stamp/Check - sybilContractInterface - .register_provider({ - contract_id: contractName, - method_name: method, - account_id_arg_name: accountIdArgName || DEFAULT_ARG_NAME, - provider_name: title, - description, - gas: providerGas, - icon_url: pinataServices.buildFileURL(iconImageCID), - external_url: externalLink, - }) - .then(() => { - hideSpinner(); - - // 4 - Show DONE Dialog -> this is going to take user to HOME page - openDialog({ dialog: DIALOGS.StampSent }); - }) - .catch((error) => { - hideSpinner(); - - const errorObj = JSON.parse(error.message); - const errorMsg = errorObj.kind.ExecutionError as string; - - openDialog({ - dialog: DIALOGS.Error, - props: { - title: "Error", - description: errorMsg, - }, - }); - }); - }, - }); - - // Updates the formik.imageURL field - useEffect(() => { - if (filesContent[0]) { - formik.setFieldValue("imageURL", filesContent[0]?.content); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [filesContent, formik.setFieldValue]); - - const goBackHanler = useCallback(() => { - router.back(); - }, [router]); - - const handleOnChange = useCallback( - (event: ChangeEvent) => { - formik.handleChange(event); - formik.setErrors({ ...formik.errors, [event.target.name]: "" }); - }, - [formik], - ); - - return ( - - - - See the Docs here - - } - > - - - Add Stamp/Check - - - A stamp is a smart-contract enabled check on NEAR to be added to - the nadabot sybil registry, to verify something about an account - whether it be a role, identity, proof of ownership, or behavior. - - - - - {/* Stamp Inputs and Preview */} - - {/* Inputs */} - - - - - - - - - - - - {/* Preview */} - - - - Stamp Card Preview - - - - - This is the preview of your submitted Stamp - - - - - - - - {/* Buttons */} - - - Go Back - - - Final Submit - - - - - ); -} diff --git a/pages/admin/index.tsx b/pages/admin/index.tsx index 2204bed..029d13a 100644 --- a/pages/admin/index.tsx +++ b/pages/admin/index.tsx @@ -1,11 +1,11 @@ import { Container } from "@mui/material"; import { useRouter } from "next/router"; +import AdminChecksSection from "@nadabot/components/admin/AdminChecksSection"; +import AdminDashboardSection from "@nadabot/components/admin/AdminDashboardSection"; +import AdminReviewChecksSection from "@nadabot/components/admin/AdminReviewChecksSection"; import ProtectedPage from "@nadabot/components/auth/ProtectedPage"; -import AdminChecksSection from "@nadabot/components/pages/admin/AdminChecksSection"; -import AdminDashboardSection from "@nadabot/components/pages/admin/AdminDashboardSection"; -import AdminReviewChecksSection from "@nadabot/components/pages/admin/AdminReviewChecksSection"; -import InvitationHeroSection from "@nadabot/components/pages/home/InvitationHeroSection"; +import InvitationHeroSection from "@nadabot/components/home/InvitationHeroSection"; import { useUser } from "@nadabot/hooks/store/useUser"; import { Routes } from "@nadabot/routes"; diff --git a/pages/index.tsx b/pages/index.tsx index b6a634d..4d1a43d 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,14 +1,14 @@ import { Container } from "@mui/material"; -import ChecksSection from "@nadabot/components/pages/home/ChecksSection"; -import CompletedSection from "@nadabot/components/pages/home/CompletedSection"; -import ExploreSection from "@nadabot/components/pages/home/ExploreSection"; -// import InvitationHeroSection from "@nadabot/components/pages/home/InvitationHeroSection"; -import PendingVerificationSection from "@nadabot/components/pages/home/PendingVerificationSection"; +import ChecksSection from "@nadabot/components/home/ChecksSection"; +import CompletedSection from "@nadabot/components/home/CompletedSection"; +import ExploreSection from "@nadabot/components/home/ExploreSection"; +import PendingVerificationSection from "@nadabot/components/home/PendingVerificationSection"; import { useUser } from "@nadabot/hooks/store/useUser"; import useCheckPendingVerification from "@nadabot/hooks/useCheckPendingVerification"; import useVerifiedProviderSuccess from "@nadabot/hooks/useVerifiedProviderSuccess"; import useViewStampURLQuery from "@nadabot/hooks/useViewStampURLQuery"; +// import InvitationHeroSection from "@nadabot/components/pages/home/InvitationHeroSection"; export default function Home() { // Show ViewProviderDialog with Verified button + Snackbar notification if user comes back from a `add_stamp` tx diff --git a/pages/stamp/create.tsx b/pages/stamp/create.tsx new file mode 100644 index 0000000..efdcb78 --- /dev/null +++ b/pages/stamp/create.tsx @@ -0,0 +1,10 @@ +import StampPageLayout from "@nadabot/components/stamp/layout"; +import { StampEditor } from "@nadabot/components/stamp/StampEditor"; + +export default function StampCreatePage() { + return ( + + + + ); +} diff --git a/pages/stamp/edit/[id].tsx b/pages/stamp/edit/[id].tsx new file mode 100644 index 0000000..3fb5482 --- /dev/null +++ b/pages/stamp/edit/[id].tsx @@ -0,0 +1,18 @@ +import { useRouter } from "next/router"; + +import StampPageLayout from "@nadabot/components/stamp/layout"; +import { StampEditor } from "@nadabot/components/stamp/StampEditor"; + +export default function StampEditPage() { + const router = useRouter(); + + if (Array.isArray(router.query.id)) { + return router.back(); + } + + return ( + + + + ); +} diff --git a/routes.ts b/routes.ts index fb00569..eb679ac 100644 --- a/routes.ts +++ b/routes.ts @@ -3,7 +3,7 @@ export const Routes = { HOME_WITH_FILTERED_CHECKS: ( filterType: "newly-created" | "active" | "deactivated" | "flagged", ) => `/?filterType=${filterType}`, - ADD_STAMP: "/add-stamp", + ADD_STAMP: "/stamp/create", ACCOUNT_INFO: (accountId: string) => `/account-info?accountId=${accountId}`, ADMIN_HOME: "/admin", }; diff --git a/services/contracts/sybil.nadabot/index.ts b/services/contracts/sybil.nadabot/index.ts index 5a6192c..7c820d1 100644 --- a/services/contracts/sybil.nadabot/index.ts +++ b/services/contracts/sybil.nadabot/index.ts @@ -13,6 +13,7 @@ import { ActivateProviderInput, DeactivateProviderInput, FlagProviderInput, + ProviderById, ProviderExternal, RegisterProviderInput, UnflagProviderInput, @@ -40,6 +41,16 @@ export const contractApi = naxiosInstance.contractApi({ */ export const get_config = () => contractApi.view<{}, Config>("get_config"); +/** + * Get Provider by its id + */ +export const get_provider = (args: ProviderById) => + contractApi.view( + "get_providers", + { args }, + { useCache: true }, + ); + /** * Get Providers * @returns diff --git a/services/contracts/sybil.nadabot/interfaces/providers.ts b/services/contracts/sybil.nadabot/interfaces/providers.ts index b60ac42..1c21c26 100644 --- a/services/contracts/sybil.nadabot/interfaces/providers.ts +++ b/services/contracts/sybil.nadabot/interfaces/providers.ts @@ -71,6 +71,10 @@ export interface ProviderExternal { stamp_validity_ms?: number | null; } +export interface ProviderById { + provider_id: string; +} + export type ProviderExternalWithIsHuman = ProviderExternal & { is_user_a_human: boolean; }; @@ -106,9 +110,6 @@ export interface ActivateProviderInput { default_weight: number; } -export interface DeactivateProviderInput { - provider_id: string; -} - +export type DeactivateProviderInput = ProviderById; export type FlagProviderInput = DeactivateProviderInput; export type UnflagProviderInput = DeactivateProviderInput; From c4875dfe2d09a6c156275371a203856eb539dd70 Mon Sep 17 00:00:00 2001 From: "Carina.Akaia.io" Date: Sun, 12 May 2024 23:53:33 +0400 Subject: [PATCH 23/81] wip: Update stamp editing styles & Use darker secondary color in MUI palette --- components/stamp/StampAdminSettings.tsx | 72 ++++++++++++---------- components/stamp/StampEditor.tsx | 79 +++++++++++++----------- components/stamp/adminSettingsForm.ts | 74 +++++++++++----------- components/stamp/settingsForm.ts | 81 ++++++++++++++----------- components/ui/CustomButton.tsx | 39 +++++++----- theme/theme.ts | 30 ++++++++- 6 files changed, 221 insertions(+), 154 deletions(-) diff --git a/components/stamp/StampAdminSettings.tsx b/components/stamp/StampAdminSettings.tsx index 0c8321e..0fe13a1 100644 --- a/components/stamp/StampAdminSettings.tsx +++ b/components/stamp/StampAdminSettings.tsx @@ -18,7 +18,7 @@ import { export type StampAdminSettingsProps = Pick< StampAdminSettingsFormParameters, - "disabled" | "providerInfo" | "onSubmit" + "disabled" | "providerInfo" | "onChange" > & { embedded?: boolean; heading?: string; @@ -32,7 +32,7 @@ export const StampAdminSettings = ({ heading = "Admin Settings", indicateUnsavedChanges, providerInfo, - onSubmit, + onChange, sx, }: StampAdminSettingsProps) => { const router = useRouter(); @@ -48,10 +48,11 @@ export const StampAdminSettings = ({ isExpiryEnabled, isDisabled, isLocked, + isSubform, isSubmitting, onExpirySwitch, values, - } = useAdminSettingsForm({ disabled, providerInfo, onSubmit }); + } = useAdminSettingsForm({ disabled, providerInfo, onChange }); useEffect( () => void indicateUnsavedChanges?.(hasChanges), @@ -59,43 +60,48 @@ export const StampAdminSettings = ({ ); const actions = useMemo( - () => ( - - - Discard - - - + isSubform ? null : ( + - Save - - - ), + + Discard + + + + Save + + + ), - [embedded, isDisabled, isSubmitting], + [embedded, isDisabled, isSubform, isSubmitting], ); return ( diff --git a/components/stamp/StampEditor.tsx b/components/stamp/StampEditor.tsx index 9a38174..10866d9 100644 --- a/components/stamp/StampEditor.tsx +++ b/components/stamp/StampEditor.tsx @@ -1,6 +1,7 @@ import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Box, Stack, Typography } from "@mui/material"; import { useRouter } from "next/router"; +import { useMemo } from "react"; import { StampCard } from "@nadabot/components/stamp/StampCard"; import CustomButton from "@nadabot/components/ui/CustomButton"; @@ -19,6 +20,7 @@ import { StampAdminSettings } from "./StampAdminSettings"; export type StampEditorProps = StampSettingsFormParameters & {}; export const StampEditor: React.FC = ({ id }) => { + const isNew = typeof id !== "string"; const { isAdmin } = useUser(); const router = useRouter(); @@ -31,9 +33,24 @@ export const StampEditor: React.FC = ({ id }) => { handleSubmit, isSubmitting, onImagePickerClick, + setValues, values, } = useSettingsForm({ id }); + const providerInfo = useMemo( + () => ({ + id: id ?? "", + status: ProviderStatus.Pending, + submitted_at_ms: 0, + submitted_by: walletApi?.accounts[0]?.accountId, + default_weight: 20, + stamp_count: 0, + ...values, + }), + + [id, values], + ); + const preview = ( = ({ id }) => { }} hidePoints providerInfo={{ - default_weight: 20, - submitted_at_ms: 0, - stamp_count: 0, - account_id_arg_name: "account_id", + ...providerInfo, + account_id_arg_name: DEFAULT_ACCOUNT_ID_ARG_NAME, status: ProviderStatus.Active, - id: "", - icon_url: values.imageURL, - submitted_by: walletApi?.accounts[0]?.accountId, - provider_name: values.title, - description: values.description, - contract_id: values.contractName, - method_name: values.method, - external_url: values.externalLink, is_user_a_human: false, }} /> ); return ( - + {/* Stamp Inputs and Preview */} = ({ id }) => { > @@ -104,34 +111,34 @@ export const StampEditor: React.FC = ({ id }) => { /> = ({ id }) => { /> @@ -151,7 +158,7 @@ export const StampEditor: React.FC = ({ id }) => { = ({ id }) => { {isAdmin && ( {}} + onChange={setValues} + {...{ providerInfo }} + providerInfo={providerInfo} /> )} @@ -207,20 +215,21 @@ export const StampEditor: React.FC = ({ id }) => { - Go Back + Cancel - Final Submit + {`${isNew ? "Submit" : "Save settings"}`} diff --git a/components/stamp/adminSettingsForm.ts b/components/stamp/adminSettingsForm.ts index 9786292..f1e9646 100644 --- a/components/stamp/adminSettingsForm.ts +++ b/components/stamp/adminSettingsForm.ts @@ -1,5 +1,5 @@ import { FormikHelpers, useFormik } from "formik"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useProviders } from "@nadabot/hooks/store/useProviders"; import * as sybilContract from "@nadabot/services/contracts/sybil.nadabot"; @@ -19,18 +19,15 @@ export type StampAdminSettingsValues = Pick< export type StampAdminSettingsFormParameters = { disabled?: boolean; providerInfo: ProviderExternal; - - onSubmit?: ( - values: StampAdminSettingsValues, - actions?: FormikHelpers, - ) => void; + onChange?: (values: StampAdminSettingsValues) => void; }; export const useAdminSettingsForm = ({ disabled, providerInfo, - onSubmit: customSubmitHandler, + onChange, }: StampAdminSettingsFormParameters) => { + const isSubform = typeof onChange === "function"; const isStampValiditySet = providerInfo.stamp_validity_ms !== null; const { updateProvider } = useProviders(); @@ -45,39 +42,41 @@ export const useAdminSettingsForm = ({ [setIsExpiryEnabled], ); - const submitHandler = useCallback( + const onSubmit = useCallback( ( { default_weight, stamp_validity_days }: StampAdminSettingsValues, { setSubmitting, resetForm }: FormikHelpers, ) => { - sybilContract - .update_provider({ - provider_id: providerInfo.id, - default_weight, - - stamp_validity_ms: isExpiryEnabled - ? daysToMilliseconds(stamp_validity_days) - : null, - }) - .then(({ id: provider_id, ...updated }) => { - updateProvider({ provider_id, ...updated }); - - resetForm({ - values: { - default_weight: updated.default_weight, - - stamp_validity_days: millisecondsToDays( - updated.stamp_validity_ms ?? 0, - ), - }, - }); - - setSubmitting(false); - }) - .catch(console.error); + if (!isSubform) { + sybilContract + .update_provider({ + provider_id: providerInfo.id, + default_weight, + + stamp_validity_ms: isExpiryEnabled + ? daysToMilliseconds(stamp_validity_days) + : null, + }) + .then(({ id: provider_id, ...updated }) => { + updateProvider({ provider_id, ...updated }); + + resetForm({ + values: { + default_weight: updated.default_weight, + + stamp_validity_days: millisecondsToDays( + updated.stamp_validity_ms ?? 0, + ), + }, + }); + + setSubmitting(false); + }) + .catch(console.error); + } }, - [isExpiryEnabled, providerInfo.id, updateProvider], + [isExpiryEnabled, isSubform, providerInfo.id, updateProvider], ); const { dirty, isSubmitting, isValid, ...form } = @@ -90,18 +89,23 @@ export const useAdminSettingsForm = ({ ), }, - onSubmit: customSubmitHandler ?? submitHandler, + onSubmit, }); const hasChanges = dirty || isExpiryEnabled !== isStampValiditySet; const isLocked = disabled || isSubmitting; + useEffect(() => { + if (isSubform) onChange(form.values); + }, [form.values, isSubform, onChange]); + return { ...form, hasChanges, isLocked, isDisabled: isLocked || !hasChanges || !isValid, isExpiryEnabled, + isSubform, isSubmitting, onExpirySwitch, }; diff --git a/components/stamp/settingsForm.ts b/components/stamp/settingsForm.ts index 4fdb789..ddf092c 100644 --- a/components/stamp/settingsForm.ts +++ b/components/stamp/settingsForm.ts @@ -6,7 +6,7 @@ import { FileSizeValidator, FileTypeValidator, } from "use-file-picker/validators"; -import { object, string } from "yup"; +import { InferType, number, object, string } from "yup"; import { DEFAULT_ACCOUNT_ID_ARG_NAME, @@ -24,11 +24,11 @@ import { ProviderExternal } from "@nadabot/services/contracts/sybil.nadabot/inte import * as pinataServices from "@nadabot/services/pinata"; const formSchema = object().shape({ - imageURL: string() + icon_url: string() .min(4, "You should attach an image") .required("Attach an image"), - title: string() + provider_name: string() .min(4, "Insert a valid title") .max( MAX_PROVIDER_NAME_LENGTH, @@ -44,23 +44,34 @@ const formSchema = object().shape({ ) .required("Insert a valid description"), - contractName: string() - .min(4, "Insert a valid contract address name") - .required("Insert a valid contract name"), + contract_id: string() + .min(4, "Insert a valid contract account id") + .required("Insert a valid contract account id"), - method: string() - .min(3, "Insert a valid method") - .required("Insert a valid method"), + method_name: string() + .min(3, "Insert a valid method name") + .required("Insert a valid method name"), - externalLink: string() + external_url: string() .min(4, "Insert a valid external link") .max( MAX_PROVIDER_EXTERNAL_URL_LENGTH, `Link shouldn't exceed ${MAX_PROVIDER_EXTERNAL_URL_LENGTH} characters`, ) .required("Insert a valid external link"), + + // Admin Settings + + default_weight: number() + .min(1, "Weight should be greater than 0") + .max(100, "Weight should be less than or equal to 100") + .optional(), + + stamp_validity_ms: number().min(0).optional(), }); +export type StampSettingsFormValues = InferType; + export type StampSettingsFormParameters = { id?: ProviderExternal["id"]; }; @@ -95,25 +106,24 @@ export const useSettingsForm = ({ id }: StampSettingsFormParameters) => { validationSchema: formSchema, initialValues: { - imageURL: "", - title: "", + icon_url: "", + provider_name: "", description: "", - contractName: "", - method: "", - externalLink: "", - - accountIdArgName: DEFAULT_ACCOUNT_ID_ARG_NAME, + contract_id: "", + method_name: "", + account_id_arg_name: DEFAULT_ACCOUNT_ID_ARG_NAME, + external_url: "", gas: 0, }, onSubmit: async ( { - title, + provider_name, description, - contractName, - method, - accountIdArgName, - externalLink, + contract_id, + method_name, + account_id_arg_name, + external_url, gas, }, @@ -122,15 +132,15 @@ export const useSettingsForm = ({ id }: StampSettingsFormParameters) => { showSpinner(); const contract = await naxiosInstance.contractApi({ - contractId: contractName, + contractId: contract_id, }); // 1 - Check contract and method: The method must have a `account_id` parameter try { - const response = await contract.view(method, { + const response = await contract.view(method_name, { // e.g.: args: {account_id: "no.account.near"} args: { - [accountIdArgName || DEFAULT_ACCOUNT_ID_ARG_NAME]: + [account_id_arg_name || DEFAULT_ACCOUNT_ID_ARG_NAME]: "no.account.near", }, }); @@ -148,10 +158,10 @@ export const useSettingsForm = ({ id }: StampSettingsFormParameters) => { return; } } catch (error) { - // 1.2 validate the `accountIdArgName` parameter or other kind of contract error + // 1.2 validate the `account_id_arg_name` parameter or other kind of contract error setFieldError( "method", - `The contract/method does not exist or does not have an "${accountIdArgName || DEFAULT_ACCOUNT_ID_ARG_NAME}" parameter.`, + `The contract/method does not exist or does not have an "${account_id_arg_name || DEFAULT_ACCOUNT_ID_ARG_NAME}" parameter.`, ); hideSpinner(); @@ -167,7 +177,7 @@ export const useSettingsForm = ({ id }: StampSettingsFormParameters) => { if (!iconImageCID) { // Validate image upload setFieldError( - "imageURL", + "icon_url", "There was an issue while trying to upload the image!", ); @@ -184,14 +194,17 @@ export const useSettingsForm = ({ id }: StampSettingsFormParameters) => { // 3 - Register Stamp/Check sybilContract .register_provider({ - contract_id: contractName, - method_name: method, - account_id_arg_name: accountIdArgName || DEFAULT_ACCOUNT_ID_ARG_NAME, - provider_name: title, + contract_id, + method_name, + + account_id_arg_name: + account_id_arg_name || DEFAULT_ACCOUNT_ID_ARG_NAME, + + provider_name, description, gas: providerGas, icon_url: pinataServices.buildFileURL(iconImageCID), - external_url: externalLink, + external_url, }) .then(() => { hideSpinner(); @@ -228,7 +241,7 @@ export const useSettingsForm = ({ id }: StampSettingsFormParameters) => { useEffect(() => { if (typeof imagePickerValue === "string") { - setFieldValue("imageURL", imagePickerValue); + setFieldValue("icon_url", imagePickerValue); } }, [filesContent, setFieldValue, imagePickerValue]); diff --git a/components/ui/CustomButton.tsx b/components/ui/CustomButton.tsx index d5d9786..c0678c5 100644 --- a/components/ui/CustomButton.tsx +++ b/components/ui/CustomButton.tsx @@ -2,6 +2,8 @@ import { Button, ButtonProps } from "@mui/material"; import CustomCircularProgress from "./CustomCircularProgress"; +type CustomButtonColor = "white" | "blue" | "beige" | "red" | "black"; + const fontSizes = { small: 14, medium: 26, @@ -14,30 +16,34 @@ const bodySizes = { large: 68, }; -const colors: Record<"white" | "blue" | "beige" | "red", ButtonProps["color"]> = - { - white: "primary", - blue: "primary", - beige: "warning", - red: "error", - }; +const colors: Record = { + white: "primary", + blue: "primary", + beige: "warning", + red: "error", + black: "secondary", +}; -const variants: Record< - "white" | "blue" | "beige" | "red", - ButtonProps["variant"] -> = { +const variants: Record = { white: "text", blue: "contained", beige: "contained", red: "contained", + black: "contained", }; -type Props = Pick< +export type CustomButtonProps = Pick< ButtonProps, - "children" | "disabled" | "onMouseOut" | "onMouseOver" | "sx" | "type" + | "children" + | "disabled" + | "onMouseOut" + | "onMouseOver" + | "sx" + | "type" + | "variant" > & { bodySize?: "small" | "medium" | "large"; - color?: "white" | "blue" | "beige" | "red"; + color?: CustomButtonColor; fontSize?: "small" | "medium" | "large"; onClick?: () => void; progress?: boolean; @@ -49,13 +55,14 @@ export default function CustomButton({ fontSize, progress = false, sx, + variant, children, ...props -}: Props) { +}: CustomButtonProps) { return (