diff --git a/.env.example b/.env.example index 4bdc5535b1..f6dff0131e 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ NEXT_PUBLIC_SENTRY_DSN=https://sentry.io SENTRY_CSP_REPORT_URI=https://sentry.io NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx -NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx +NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=xxx NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=xxx NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx diff --git a/.eslintrc.js b/.eslintrc.js index 49e2ad4c43..b17253eb63 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -7,7 +7,7 @@ const RESTRICTED_MODULES = { { name: 'playwright/TestApp', message: 'Please use render() fixture from test() function of playwright/lib module' }, { name: '@chakra-ui/react', - importNames: [ 'Popover', 'Menu', 'useToast' ], + importNames: [ 'Popover', 'Menu', 'PinInput', 'useToast' ], message: 'Please use corresponding component or hook from ui/shared/chakra component instead', }, { diff --git a/configs/app/features/account.ts b/configs/app/features/account.ts index 120a94b7e9..56cf1d1eaa 100644 --- a/configs/app/features/account.ts +++ b/configs/app/features/account.ts @@ -1,45 +1,16 @@ import type { Feature } from './types'; -import stripTrailingSlash from 'lib/stripTrailingSlash'; - -import app from '../app'; +import services from '../services'; import { getEnvValue } from '../utils'; -const authUrl = stripTrailingSlash(getEnvValue('NEXT_PUBLIC_AUTH_URL') || app.baseUrl); - -const logoutUrl = (() => { - try { - const envUrl = getEnvValue('NEXT_PUBLIC_LOGOUT_URL'); - const auth0ClientId = getEnvValue('NEXT_PUBLIC_AUTH0_CLIENT_ID'); - const returnUrl = authUrl + '/auth/logout'; - - if (!envUrl || !auth0ClientId) { - throw Error(); - } - - const url = new URL(envUrl); - url.searchParams.set('client_id', auth0ClientId); - url.searchParams.set('returnTo', returnUrl); - - return url.toString(); - } catch (error) { - return; - } -})(); - const title = 'My account'; -const config: Feature<{ authUrl: string; logoutUrl: string }> = (() => { - if ( - getEnvValue('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED') === 'true' && - authUrl && - logoutUrl - ) { +const config: Feature<{ isEnabled: true; recaptchaSiteKey: string }> = (() => { + if (getEnvValue('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED') === 'true' && services.reCaptchaV3.siteKey) { return Object.freeze({ title, isEnabled: true, - authUrl, - logoutUrl, + recaptchaSiteKey: services.reCaptchaV3.siteKey, }); } diff --git a/configs/app/features/csvExport.ts b/configs/app/features/csvExport.ts index 442b4bedc7..eecbff4464 100644 --- a/configs/app/features/csvExport.ts +++ b/configs/app/features/csvExport.ts @@ -5,12 +5,12 @@ import services from '../services'; const title = 'Export data to CSV file'; const config: Feature<{ reCaptcha: { siteKey: string }}> = (() => { - if (services.reCaptcha.siteKey) { + if (services.reCaptchaV3.siteKey) { return Object.freeze({ title, isEnabled: true, reCaptcha: { - siteKey: services.reCaptcha.siteKey, + siteKey: services.reCaptchaV3.siteKey, }, }); } diff --git a/configs/app/features/publicTagsSubmission.ts b/configs/app/features/publicTagsSubmission.ts index 296d5e143a..ab1b0e33d2 100644 --- a/configs/app/features/publicTagsSubmission.ts +++ b/configs/app/features/publicTagsSubmission.ts @@ -9,7 +9,7 @@ const apiHost = getEnvValue('NEXT_PUBLIC_ADMIN_SERVICE_API_HOST'); const title = 'Public tag submission'; const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => { - if (services.reCaptcha.siteKey && addressMetadata.isEnabled && apiHost) { + if (services.reCaptchaV3.siteKey && addressMetadata.isEnabled && apiHost) { return Object.freeze({ title, isEnabled: true, diff --git a/configs/app/services.ts b/configs/app/services.ts index 86df538215..1472f00f82 100644 --- a/configs/app/services.ts +++ b/configs/app/services.ts @@ -1,7 +1,7 @@ import { getEnvValue } from './utils'; export default Object.freeze({ - reCaptcha: { - siteKey: getEnvValue('NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY'), + reCaptchaV3: { + siteKey: getEnvValue('NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY'), }, }); diff --git a/configs/envs/.env.jest b/configs/envs/.env.jest index abe2107a80..7de7ee11fc 100644 --- a/configs/envs/.env.jest +++ b/configs/envs/.env.jest @@ -49,4 +49,4 @@ NEXT_PUBLIC_AUTH0_CLIENT_ID=xxx NEXT_PUBLIC_STATS_API_HOST=https://localhost:3004 NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://localhost:3005 NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://localhost:3006 -NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx +NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=xxx diff --git a/configs/envs/.env.pw b/configs/envs/.env.pw index dec30c09fe..95b518074a 100644 --- a/configs/envs/.env.pw +++ b/configs/envs/.env.pw @@ -52,5 +52,5 @@ NEXT_PUBLIC_CONTRACT_INFO_API_HOST=http://localhost:3005 NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=http://localhost:3006 NEXT_PUBLIC_METADATA_SERVICE_API_HOST=http://localhost:3007 NEXT_PUBLIC_NAME_SERVICE_API_HOST=http://localhost:3008 -NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx +NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=xxx NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx diff --git a/deploy/tools/envs-validator/index.ts b/deploy/tools/envs-validator/index.ts index b867577fde..3a0665054a 100644 --- a/deploy/tools/envs-validator/index.ts +++ b/deploy/tools/envs-validator/index.ts @@ -148,4 +148,15 @@ function printDeprecationWarning(envsMap: Record) { console.warn('The NEXT_PUBLIC_HOMEPAGE_PLATE_TEXT_COLOR and NEXT_PUBLIC_HOMEPAGE_PLATE_BACKGROUND variables are now deprecated and will be removed in the next release. Please migrate to the NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG variable.'); console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n'); } + + if ( + envsMap.NEXT_PUBLIC_AUTH0_CLIENT_ID || + envsMap.NEXT_PUBLIC_AUTH_URL || + envsMap.NEXT_PUBLIC_LOGOUT_URL + ) { + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗'); + // eslint-disable-next-line max-len + console.warn('The NEXT_PUBLIC_AUTH0_CLIENT_ID, NEXT_PUBLIC_AUTH_URL and NEXT_PUBLIC_LOGOUT_URL variables are now deprecated and will be removed in the next release.'); + console.log('❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗\n'); + } } diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index b3d55d788f..426f0cdc84 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -839,7 +839,7 @@ const schema = yup // 6. External services envs NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), - NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: yup.string(), + NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY: yup.string(), NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: yup.string(), NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: yup.string(), NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: yup.string(), diff --git a/deploy/tools/envs-validator/test/.env.base b/deploy/tools/envs-validator/test/.env.base index 7ffdb69648..cc0c5869c6 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -3,7 +3,7 @@ NEXT_PUBLIC_AUTH_URL=https://example.com NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true NEXT_PUBLIC_LOGOUT_URL=https://example.com NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx -NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx +NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY=xxx NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=UA-XXXXXX-X NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN=xxx NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY=xxx diff --git a/deploy/values/review-l2/values.yaml.gotmpl b/deploy/values/review-l2/values.yaml.gotmpl index d03f0984eb..e1853861cf 100644 --- a/deploy/values/review-l2/values.yaml.gotmpl +++ b/deploy/values/review-l2/values.yaml.gotmpl @@ -77,7 +77,7 @@ frontend: SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID - NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY NEXT_PUBLIC_OG_IMAGE_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/base-mainnet.png + NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY diff --git a/deploy/values/review/values.yaml.gotmpl b/deploy/values/review/values.yaml.gotmpl index 5b2623182f..f3ef7136cf 100644 --- a/deploy/values/review/values.yaml.gotmpl +++ b/deploy/values/review/values.yaml.gotmpl @@ -83,8 +83,8 @@ frontend: SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID - NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID FAVICON_GENERATOR_API_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_FAVICON_GENERATOR_API_KEY NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GROWTH_BOOK_CLIENT_KEY NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_MIXPANEL_PROJECT_TOKEN + NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY: ref+vault://deployment-values/blockscout/common?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY diff --git a/docs/DEPRECATED_ENVS.md b/docs/DEPRECATED_ENVS.md index e00aea9307..d6472a0676 100644 --- a/docs/DEPRECATED_ENVS.md +++ b/docs/DEPRECATED_ENVS.md @@ -10,4 +10,5 @@ | NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` | Set to false if network doesn't have gas tracker | - | `true` | `false` | - | v1.25.0 | Replaced by NEXT_PUBLIC_GAS_TRACKER_ENABLED | | NEXT_PUBLIC_NETWORK_GOVERNANCE_TOKEN_SYMBOL | `string` | Network governance token symbol | - | - | `GNO` | v1.12.0 | v1.29.0 | Replaced by NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | | NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` | v1.24.0 | v1.31.0 | Replaced by NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS | -| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` | v1.0.x+ | v1.35.0 | Replaces by NEXT_PUBLIC_HOMEPAGE_STATS \ No newline at end of file +| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` | v1.0.x+ | v1.35.0 | Replaced by NEXT_PUBLIC_HOMEPAGE_STATS | +| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | Google reCAPTCHA v2 site key | - | - | `` | v1.36.0 | Replaced by NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY | diff --git a/docs/ENVS.md b/docs/ENVS.md index 1a38d9d7bc..79b7ff4353 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -342,9 +342,10 @@ Settings for meta tags, OG tags and SEO | Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | --- | --- | --- | --- | --- | --- | --- | | NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED | `boolean` | Set to true if network has account feature | Required | - | `true` | v1.0.x+ | -| NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` | Client id for [Auth0](https://auth0.com/) provider | Required | - | `` | v1.0.x+ | -| NEXT_PUBLIC_AUTH_URL | `string` | Account auth base url; it is used for building login URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/auth0`) and logout return URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/logout`); if not provided the base app URL will be used instead | Required | - | `https://blockscout.com` | v1.0.x+ | -| NEXT_PUBLIC_LOGOUT_URL | `string` | Account logout url. Required if account is supported for the app instance. | Required | - | `https://blockscoutcom.us.auth0.com/v2/logout` | v1.0.x+ | +| NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY | `boolean` | See [below](ENVS.md#google-recaptcha) | Required | - | `` | v1.36.0+ | +| NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` | **DEPRECATED** Client id for [Auth0](https://auth0.com/) provider | - | - | `` | v1.0.x+ | +| NEXT_PUBLIC_AUTH_URL | `string` | **DEPRECATED** Account auth base url; it is used for building login URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/auth0`) and logout return URL (`${ NEXT_PUBLIC_AUTH_URL }/auth/logout`); if not provided the base app URL will be used instead | - | - | `https://blockscout.com` | v1.0.x+ | +| NEXT_PUBLIC_LOGOUT_URL | `string` | **DEPRECATED** Account logout url. Required if account is supported for the app instance. | - | - | `https://blockscoutcom.us.auth0.com/v2/logout` | v1.0.x+ |   @@ -442,7 +443,7 @@ This feature is **enabled by default** with the `coinzilla` ads provider. To swi | Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | --- | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | See [below](ENVS.md#google-recaptcha) | true | - | `` | v1.0.x+ | +| NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY | `string` | See [below](ENVS.md#google-recaptcha) | true | - | `` | v1.36.0+ |   @@ -801,4 +802,4 @@ For obtaining the variables values please refer to [reCAPTCHA documentation](htt | Variable | Type| Description | Compulsoriness | Default value | Example value | Version | | --- | --- | --- | --- | --- | --- | --- | -| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `string` | Site key | - | - | `` | v1.0.x+ | +| NEXT_PUBLIC_RE_CAPTCHA_V3_APP_SITE_KEY | `string` | Google reCAPTCHA v3 site key | - | - | `` | v1.36.0+ | diff --git a/icons/API_slim.svg b/icons/API_slim.svg new file mode 100644 index 0000000000..1473387362 --- /dev/null +++ b/icons/API_slim.svg @@ -0,0 +1,3 @@ + + + diff --git a/icons/email-sent.svg b/icons/email-sent.svg deleted file mode 100644 index d31e30f244..0000000000 --- a/icons/email-sent.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/icons/private_tags_slim.svg b/icons/private_tags_slim.svg new file mode 100644 index 0000000000..599cff3349 --- /dev/null +++ b/icons/private_tags_slim.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/icons/sign_out.svg b/icons/sign_out.svg new file mode 100644 index 0000000000..b577078676 --- /dev/null +++ b/icons/sign_out.svg @@ -0,0 +1,4 @@ + + + + diff --git a/icons/verified_slim.svg b/icons/verified_slim.svg new file mode 100644 index 0000000000..a13930aab2 --- /dev/null +++ b/icons/verified_slim.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/api/resources.ts b/lib/api/resources.ts index 4229430c18..6b58cd6807 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -168,9 +168,6 @@ export const RESOURCES = { user_info: { path: '/api/account/v2/user/info', }, - email_resend: { - path: '/api/account/v2/email/resend', - }, custom_abi: { path: '/api/account/v2/user/custom_abis{/:id}', pathParams: [ 'id' as const ], @@ -228,6 +225,26 @@ export const RESOURCES = { needAuth: true, }, + // AUTH + auth_send_otp: { + path: '/api/account/v2/send_otp', + }, + auth_confirm_otp: { + path: '/api/account/v2/confirm_otp', + }, + auth_siwe_message: { + path: '/api/account/v2/siwe_message', + }, + auth_siwe_verify: { + path: '/api/account/v2/authenticate_via_wallet', + }, + auth_link_email: { + path: '/api/account/v2/email/link', + }, + auth_link_address: { + path: '/api/account/v2/address/link', + }, + // STATS MICROSERVICE API stats_counters: { path: '/api/v1/counters', diff --git a/lib/contexts/marketplace.tsx b/lib/contexts/marketplace.tsx index 2aba76e39b..59c7b34bc5 100644 --- a/lib/contexts/marketplace.tsx +++ b/lib/contexts/marketplace.tsx @@ -10,7 +10,7 @@ type TMarketplaceContext = { setIsAutoConnectDisabled: (isAutoConnectDisabled: boolean) => void; } -const MarketplaceContext = createContext({ +export const MarketplaceContext = createContext({ isAutoConnectDisabled: false, setIsAutoConnectDisabled: () => {}, }); diff --git a/lib/cookies.ts b/lib/cookies.ts index cef6a38a42..f4cf66debc 100644 --- a/lib/cookies.ts +++ b/lib/cookies.ts @@ -5,8 +5,6 @@ import isBrowser from './isBrowser'; export enum NAMES { NAV_BAR_COLLAPSED='nav_bar_collapsed', API_TOKEN='_explorer_key', - INVALID_SESSION='invalid_session', - CONFIRM_EMAIL_PAGE_VIEWED='confirm_email_page_viewed', TXS_SORT='txs_sort', COLOR_MODE='chakra-ui-color-mode', COLOR_MODE_HEX='chakra-ui-color-mode-hex', @@ -28,12 +26,16 @@ export function get(name?: NAMES | undefined | null, serverCookie?: string) { } } -export function set(name: string, value: string, attributes: Cookies.CookieAttributes = {}) { +export function set(name: NAMES, value: string, attributes: Cookies.CookieAttributes = {}) { attributes.path = '/'; return Cookies.set(name, value, attributes); } +export function remove(name: NAMES, attributes: Cookies.CookieAttributes = {}) { + return Cookies.remove(name, attributes); +} + export function getFromCookieString(cookieString: string, name?: NAMES | undefined | null) { return cookieString.split(`${ name }=`)[1]?.split(';')[0]; } diff --git a/lib/errors/getErrorMessage.ts b/lib/errors/getErrorMessage.ts new file mode 100644 index 0000000000..5e15f29a1c --- /dev/null +++ b/lib/errors/getErrorMessage.ts @@ -0,0 +1,6 @@ +import getErrorObj from './getErrorObj'; + +export default function getErrorMessage(error: unknown): string | undefined { + const errorObj = getErrorObj(error); + return errorObj && 'message' in errorObj && typeof errorObj.message === 'string' ? errorObj.message : undefined; +} diff --git a/lib/hooks/useGetCsrfToken.tsx b/lib/hooks/useGetCsrfToken.tsx index 297f1567c2..a301b25594 100644 --- a/lib/hooks/useGetCsrfToken.tsx +++ b/lib/hooks/useGetCsrfToken.tsx @@ -10,7 +10,7 @@ import useFetch from 'lib/hooks/useFetch'; export default function useGetCsrfToken() { const nodeApiFetch = useFetch(); - useQuery({ + return useQuery({ queryKey: getResourceKey('csrf'), queryFn: async() => { if (!isNeedProxy()) { diff --git a/lib/hooks/useIsAccountActionAllowed.tsx b/lib/hooks/useIsAccountActionAllowed.tsx deleted file mode 100644 index c8df0912a3..0000000000 --- a/lib/hooks/useIsAccountActionAllowed.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { useQueryClient } from '@tanstack/react-query'; -import React from 'react'; - -import type { UserInfo } from 'types/api/account'; - -import { resourceKey } from 'lib/api/resources'; -import useLoginUrl from 'lib/hooks/useLoginUrl'; - -export default function useIsAccountActionAllowed() { - const queryClient = useQueryClient(); - - const profileData = queryClient.getQueryData([ resourceKey('user_info') ]); - const isAuth = Boolean(profileData); - const loginUrl = useLoginUrl(); - - return React.useCallback(() => { - if (!loginUrl) { - return false; - } - - if (!isAuth) { - window.location.assign(loginUrl); - return false; - } - - return true; - }, [ isAuth, loginUrl ]); -} diff --git a/lib/hooks/useLoginUrl.tsx b/lib/hooks/useLoginUrl.tsx deleted file mode 100644 index a111d1eebb..0000000000 --- a/lib/hooks/useLoginUrl.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useRouter } from 'next/router'; - -import { route } from 'nextjs-routes'; - -import config from 'configs/app'; - -const feature = config.features.account; - -export default function useLoginUrl() { - const router = useRouter(); - return feature.isEnabled ? - feature.authUrl + route({ pathname: '/auth/auth0', query: { path: router.asPath } }) : - undefined; -} diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index 5814aae2ee..aa9f55d053 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -5,12 +5,10 @@ import type { NavItemInternal, NavItem, NavGroupItem } from 'types/client/naviga import config from 'configs/app'; import { rightLineArrow } from 'lib/html-entities'; -import UserAvatar from 'ui/shared/UserAvatar'; interface ReturnType { mainNavItems: Array; accountNavItems: Array; - profileItem: NavItem; } export function isGroupItem(item: NavItem | NavGroupItem): item is NavGroupItem { @@ -312,13 +310,6 @@ export default function useNavItems(): ReturnType { }, ].filter(Boolean); - const profileItem = { - text: 'My profile', - nextRoute: { pathname: '/auth/profile' as const }, - iconComponent: UserAvatar, - isActive: pathname === '/auth/profile', - }; - - return { mainNavItems, accountNavItems, profileItem }; + return { mainNavItems, accountNavItems }; }, [ pathname ]); } diff --git a/lib/hooks/useRedirectForInvalidAuthToken.tsx b/lib/hooks/useRedirectForInvalidAuthToken.tsx deleted file mode 100644 index 24b74ace64..0000000000 --- a/lib/hooks/useRedirectForInvalidAuthToken.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import * as Sentry from '@sentry/react'; -import { useQueryClient } from '@tanstack/react-query'; -import React from 'react'; - -import { resourceKey } from 'lib/api/resources'; -import type { ResourceError } from 'lib/api/resources'; -import * as cookies from 'lib/cookies'; -import useLoginUrl from 'lib/hooks/useLoginUrl'; - -export default function useRedirectForInvalidAuthToken() { - const queryClient = useQueryClient(); - - const state = queryClient.getQueryState([ resourceKey('user_info') ]); - const errorStatus = state?.error?.status; - const loginUrl = useLoginUrl(); - - React.useEffect(() => { - if (errorStatus === 401) { - const apiToken = cookies.get(cookies.NAMES.API_TOKEN); - - if (apiToken && loginUrl) { - Sentry.captureException(new Error('Invalid API token'), { tags: { source: 'invalid_api_token' } }); - window.location.assign(loginUrl); - } - } - }, [ errorStatus, loginUrl ]); -} diff --git a/lib/metadata/getPageOgType.ts b/lib/metadata/getPageOgType.ts index 38d6008db6..7159ae3000 100644 --- a/lib/metadata/getPageOgType.ts +++ b/lib/metadata/getPageOgType.ts @@ -65,8 +65,6 @@ const OG_TYPE_DICT: Record = { '/api/healthz': 'Regular page', '/api/config': 'Regular page', '/api/sprite': 'Regular page', - '/auth/auth0': 'Regular page', - '/auth/unverified-email': 'Regular page', }; export default function getPageOgType(pathname: Route['pathname']) { diff --git a/lib/metadata/templates/description.ts b/lib/metadata/templates/description.ts index 2d0067474d..30452a5041 100644 --- a/lib/metadata/templates/description.ts +++ b/lib/metadata/templates/description.ts @@ -69,8 +69,6 @@ const TEMPLATE_MAP: Record = { '/api/healthz': DEFAULT_TEMPLATE, '/api/config': DEFAULT_TEMPLATE, '/api/sprite': DEFAULT_TEMPLATE, - '/auth/auth0': DEFAULT_TEMPLATE, - '/auth/unverified-email': DEFAULT_TEMPLATE, }; const TEMPLATE_MAP_ENHANCED: Partial> = { diff --git a/lib/metadata/templates/title.ts b/lib/metadata/templates/title.ts index b026975bc0..ed65a43528 100644 --- a/lib/metadata/templates/title.ts +++ b/lib/metadata/templates/title.ts @@ -65,8 +65,6 @@ const TEMPLATE_MAP: Record = { '/api/healthz': '%network_name% node API health check', '/api/config': '%network_name% node API app config', '/api/sprite': '%network_name% node API SVG sprite content', - '/auth/auth0': '%network_name% authentication', - '/auth/unverified-email': '%network_name% unverified email', }; const TEMPLATE_MAP_ENHANCED: Partial> = { diff --git a/lib/mixpanel/getPageType.ts b/lib/mixpanel/getPageType.ts index 8570aa1ac7..176046f379 100644 --- a/lib/mixpanel/getPageType.ts +++ b/lib/mixpanel/getPageType.ts @@ -63,8 +63,6 @@ export const PAGE_TYPE_DICT: Record = { '/api/healthz': 'Node API: Health check', '/api/config': 'Node API: App config', '/api/sprite': 'Node API: SVG sprite content', - '/auth/auth0': 'Auth', - '/auth/unverified-email': 'Unverified email', }; export default function getPageType(pathname: Route['pathname']) { diff --git a/lib/mixpanel/utils.ts b/lib/mixpanel/utils.ts index f29756f1b3..76d7b89e5a 100644 --- a/lib/mixpanel/utils.ts +++ b/lib/mixpanel/utils.ts @@ -7,6 +7,8 @@ export enum EventTypes { LOCAL_SEARCH = 'Local search', ADD_TO_WALLET = 'Add to wallet', ACCOUNT_ACCESS = 'Account access', + LOGIN = 'Login', + ACCOUNT_LINK_INFO = 'Account link info', PRIVATE_TAG = 'Private tag', VERIFY_ADDRESS = 'Verify address', VERIFY_TOKEN = 'Verify token', @@ -54,7 +56,27 @@ Type extends EventTypes.ADD_TO_WALLET ? ( } ) : Type extends EventTypes.ACCOUNT_ACCESS ? { - 'Action': 'Auth0 init' | 'Verification email resent' | 'Logged out'; + 'Action': 'Dropdown open' | 'Logged out'; +} : +Type extends EventTypes.LOGIN ? ( + { + 'Action': 'Started'; + 'Source': string; + } | { + 'Action': 'Wallet' | 'Email'; + 'Source': 'Options selector'; + } | { + 'Action': 'OTP sent'; + 'Source': 'Email'; + } | { + 'Action': 'Success'; + 'Source': 'Email' | 'Wallet'; + } +) : +Type extends EventTypes.ACCOUNT_LINK_INFO ? { + 'Source': 'Profile' | 'Login modal' | 'Profile dropdown'; + 'Status': 'Started' | 'OTP sent' | 'Finished'; + 'Type': 'Email' | 'Wallet'; } : Type extends EventTypes.PRIVATE_TAG ? { 'Action': 'Form opened' | 'Submit'; @@ -75,7 +97,7 @@ Type extends EventTypes.VERIFY_TOKEN ? { 'Action': 'Form opened' | 'Submit'; } : Type extends EventTypes.WALLET_CONNECT ? { - 'Source': 'Header' | 'Smart contracts' | 'Swap button'; + 'Source': 'Header' | 'Login' | 'Profile' | 'Profile dropdown' | 'Smart contracts' | 'Swap button'; 'Status': 'Started' | 'Connected'; } : Type extends EventTypes.WALLET_ACTION ? ( diff --git a/lib/validations/address.ts b/lib/validations/address.ts deleted file mode 100644 index cfca644c41..0000000000 --- a/lib/validations/address.ts +++ /dev/null @@ -1,5 +0,0 @@ -// maybe it depends on the network?? - -export const ADDRESS_REGEXP = /^0x[a-fA-F\d]{40}$/; - -export const ADDRESS_LENGTH = 42; diff --git a/lib/validations/url.ts b/lib/validations/url.ts deleted file mode 100644 index b4b30d4e05..0000000000 --- a/lib/validations/url.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const validator = (value: string | undefined) => { - if (!value) { - return true; - } - - try { - new URL(value); - return true; - } catch (error) { - return 'Incorrect URL'; - } -}; diff --git a/lib/web3/useAccountWithDomain.ts b/lib/web3/useAccountWithDomain.ts new file mode 100644 index 0000000000..0eeb18e4c5 --- /dev/null +++ b/lib/web3/useAccountWithDomain.ts @@ -0,0 +1,31 @@ +import React from 'react'; + +import config from 'configs/app'; +import useApiQuery from 'lib/api/useApiQuery'; + +import useAccount from './useAccount'; + +export default function useAccountWithDomain(isEnabled: boolean) { + const { address } = useAccount(); + + const isQueryEnabled = config.features.nameService.isEnabled && Boolean(address) && Boolean(isEnabled); + + const domainQuery = useApiQuery('address_domain', { + pathParams: { + chainId: config.chain.id, + address, + }, + queryOptions: { + enabled: isQueryEnabled, + refetchOnMount: false, + }, + }); + + return React.useMemo(() => { + return { + address: isEnabled ? address : undefined, + domain: domainQuery.data?.domain?.name, + isLoading: isQueryEnabled && domainQuery.isLoading, + }; + }, [ address, domainQuery.data?.domain?.name, domainQuery.isLoading, isEnabled, isQueryEnabled ]); +} diff --git a/ui/snippets/walletMenu/useWallet.tsx b/lib/web3/useWallet.ts similarity index 72% rename from ui/snippets/walletMenu/useWallet.tsx rename to lib/web3/useWallet.ts index 9b252dded9..6d95671fcd 100644 --- a/ui/snippets/walletMenu/useWallet.tsx +++ b/lib/web3/useWallet.ts @@ -8,11 +8,11 @@ interface Params { source: mixpanel.EventPayload['Source']; } -export default function useWallet({ source }: Params) { - const { open } = useWeb3Modal(); +export default function useWeb3Wallet({ source }: Params) { + const { open: openModal } = useWeb3Modal(); const { open: isOpen } = useWeb3ModalState(); const { disconnect } = useDisconnect(); - const [ isModalOpening, setIsModalOpening ] = React.useState(false); + const [ isOpening, setIsOpening ] = React.useState(false); const [ isClientLoaded, setIsClientLoaded ] = React.useState(false); const isConnectionStarted = React.useRef(false); @@ -21,12 +21,12 @@ export default function useWallet({ source }: Params) { }, []); const handleConnect = React.useCallback(async() => { - setIsModalOpening(true); - await open(); - setIsModalOpening(false); + setIsOpening(true); + await openModal(); + setIsOpening(false); mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Started' }); isConnectionStarted.current = true; - }, [ open, source ]); + }, [ openModal, source ]); const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => { if (!isReconnected && isConnectionStarted.current) { @@ -46,15 +46,14 @@ export default function useWallet({ source }: Params) { const { address, isDisconnected } = useAccount(); - const isWalletConnected = isClientLoaded && !isDisconnected && address !== undefined; + const isConnected = isClientLoaded && !isDisconnected && address !== undefined; - return { - openModal: open, - isWalletConnected, - address: address || '', + return React.useMemo(() => ({ connect: handleConnect, disconnect: handleDisconnect, - isModalOpening, - isModalOpen: isOpen, - }; + isOpen: isOpening || isOpen, + isConnected, + address, + openModal, + }), [ handleConnect, handleDisconnect, isOpen, isOpening, isConnected, address, openModal ]); } diff --git a/mocks/user/profile.ts b/mocks/user/profile.ts index 955f872e01..e0178bc140 100644 --- a/mocks/user/profile.ts +++ b/mocks/user/profile.ts @@ -1,13 +1,17 @@ -export const base = { +import type { UserInfo } from 'types/api/account'; + +export const base: UserInfo = { avatar: 'https://avatars.githubusercontent.com/u/22130104', email: 'tom@ohhhh.me', name: 'tom goriunov', nickname: 'tom2drum', + address_hash: null, }; -export const withoutEmail = { +export const withoutEmail: UserInfo = { avatar: 'https://avatars.githubusercontent.com/u/22130104', email: null, name: 'tom goriunov', nickname: 'tom2drum', + address_hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859', }; diff --git a/nextjs/csp/policies/googleReCaptcha.ts b/nextjs/csp/policies/googleReCaptcha.ts index d759e70d59..55826d250d 100644 --- a/nextjs/csp/policies/googleReCaptcha.ts +++ b/nextjs/csp/policies/googleReCaptcha.ts @@ -3,7 +3,7 @@ import type CspDev from 'csp-dev'; import config from 'configs/app'; export function googleReCaptcha(): CspDev.DirectiveDescriptor { - if (!config.services.reCaptcha.siteKey) { + if (!config.services.reCaptchaV3.siteKey) { return {}; } diff --git a/nextjs/getServerSideProps.ts b/nextjs/getServerSideProps.ts index c9933fb679..ecf6277d44 100644 --- a/nextjs/getServerSideProps.ts +++ b/nextjs/getServerSideProps.ts @@ -6,9 +6,9 @@ import type { RollupType } from 'types/client/rollup'; import type { Route } from 'nextjs-routes'; import config from 'configs/app'; -import isNeedProxy from 'lib/api/isNeedProxy'; const rollupFeature = config.features.rollup; const adBannerFeature = config.features.adsBanner; +import isNeedProxy from 'lib/api/isNeedProxy'; import type * as metadata from 'lib/metadata'; export interface Props { diff --git a/nextjs/middlewares/account.ts b/nextjs/middlewares/account.ts index e46c5a07fe..7ab562542f 100644 --- a/nextjs/middlewares/account.ts +++ b/nextjs/middlewares/account.ts @@ -1,10 +1,7 @@ import type { NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; -import { route } from 'nextjs-routes'; - import config from 'configs/app'; -import { DAY } from 'lib/consts'; import * as cookies from 'lib/cookies'; export function account(req: NextRequest) { @@ -25,37 +22,7 @@ export function account(req: NextRequest) { const isProfileRoute = req.nextUrl.pathname.includes('/auth/profile'); if ((isAccountRoute || isProfileRoute)) { - const authUrl = feature.authUrl + route({ pathname: '/auth/auth0', query: { path: req.nextUrl.pathname } }); - return NextResponse.redirect(authUrl); - } - } - - // if user hasn't confirmed email yet - if (req.cookies.get(cookies.NAMES.INVALID_SESSION)) { - // if user has both cookies, make redirect to logout - if (apiTokenCookie) { - // yes, we could have checked that the current URL is not the logout URL, but we hadn't - // logout URL is always external URL in auth0.com sub-domain - // at least we hope so - - const res = NextResponse.redirect(feature.logoutUrl); - res.cookies.delete(cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED); // reset cookie to show email verification page again - - return res; - } - - // if user hasn't seen email verification page, make redirect to it - if (!req.cookies.get(cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED)) { - if (!req.nextUrl.pathname.includes('/auth/unverified-email')) { - const url = config.app.baseUrl + route({ pathname: '/auth/unverified-email' }); - const res = NextResponse.redirect(url); - res.cookies.set({ - name: cookies.NAMES.CONFIRM_EMAIL_PAGE_VIEWED, - value: 'true', - expires: Date.now() + 7 * DAY, - }); - return res; - } + return NextResponse.redirect(config.app.baseUrl); } } } diff --git a/nextjs/nextjs-routes.d.ts b/nextjs/nextjs-routes.d.ts index aee33cd320..41b2bdd9f1 100644 --- a/nextjs/nextjs-routes.d.ts +++ b/nextjs/nextjs-routes.d.ts @@ -28,9 +28,7 @@ declare module "nextjs-routes" { | StaticRoute<"/api-docs"> | DynamicRoute<"/apps/[id]", { "id": string }> | StaticRoute<"/apps"> - | StaticRoute<"/auth/auth0"> | StaticRoute<"/auth/profile"> - | StaticRoute<"/auth/unverified-email"> | DynamicRoute<"/batches/[number]", { "number": string }> | StaticRoute<"/batches"> | DynamicRoute<"/blobs/[hash]", { "hash": string }> diff --git a/nextjs/utils/fetchProxy.ts b/nextjs/utils/fetchProxy.ts index 0081781215..0c52c9f80e 100644 --- a/nextjs/utils/fetchProxy.ts +++ b/nextjs/utils/fetchProxy.ts @@ -33,6 +33,7 @@ export default function fetchFactory( message: 'API fetch via Next.js proxy', url, // headers, + // init, }); const body = (() => { diff --git a/package.json b/package.json index 1ae2f9f263..06016e4572 100644 --- a/package.json +++ b/package.json @@ -100,8 +100,8 @@ "react": "18.2.0", "react-device-detect": "^2.2.3", "react-dom": "18.2.0", - "react-google-recaptcha": "^3.1.0", - "react-hook-form": "^7.33.1", + "react-google-recaptcha-v3": "1.10.1", + "react-hook-form": "7.52.1", "react-identicons": "^1.2.5", "react-intersection-observer": "^9.5.2", "react-jazzicon": "^1.0.4", diff --git a/pages/api/proxy.ts b/pages/api/proxy.ts index 7e8c0aff38..78d8e152ba 100644 --- a/pages/api/proxy.ts +++ b/pages/api/proxy.ts @@ -22,8 +22,13 @@ const handler = async(nextReq: NextApiRequest, nextRes: NextApiResponse) => { ); // proxy some headers from API - nextRes.setHeader('x-request-id', apiRes.headers.get('x-request-id') || ''); - nextRes.setHeader('set-cookie', apiRes.headers.get('set-cookie') || ''); + const requestId = apiRes.headers.get('x-request-id'); + requestId && nextRes.setHeader('x-request-id', requestId); + + const setCookie = apiRes.headers.raw()['set-cookie']; + setCookie?.forEach((value) => { + nextRes.appendHeader('set-cookie', value); + }); nextRes.setHeader('content-type', apiRes.headers.get('content-type') || ''); nextRes.status(apiRes.status).send(apiRes.body); diff --git a/pages/auth/auth0.tsx b/pages/auth/auth0.tsx deleted file mode 100644 index 5bf73de5f1..0000000000 --- a/pages/auth/auth0.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { NextPage } from 'next'; - -const Page: NextPage = () => { - return null; -}; - -export default Page; - -export async function getServerSideProps() { - return { - notFound: true, - }; -} diff --git a/pages/auth/unverified-email.tsx b/pages/auth/unverified-email.tsx deleted file mode 100644 index 03b2cfe150..0000000000 --- a/pages/auth/unverified-email.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { NextPage } from 'next'; -import React from 'react'; - -import PageNextJs from 'nextjs/PageNextJs'; - -import UnverifiedEmail from 'ui/pages/UnverifiedEmail'; - -const Page: NextPage = () => { - return ( - - - - ); -}; - -export default Page; - -export { account as getServerSideProps } from 'nextjs/getServerSideProps'; diff --git a/playwright/TestApp.tsx b/playwright/TestApp.tsx index 540afac5ac..07b5c12b34 100644 --- a/playwright/TestApp.tsx +++ b/playwright/TestApp.tsx @@ -10,6 +10,7 @@ import type { Props as PageProps } from 'nextjs/getServerSideProps'; import config from 'configs/app'; import { AppContextProvider } from 'lib/contexts/app'; +import { MarketplaceContext } from 'lib/contexts/marketplace'; import { SocketProvider } from 'lib/socket/context'; import currentChain from 'lib/web3/currentChain'; import theme from 'theme/theme'; @@ -23,6 +24,10 @@ export type Props = { appContext?: { pageProps: PageProps; }; + marketplaceContext?: { + isAutoConnectDisabled: boolean; + setIsAutoConnectDisabled: (isAutoConnectDisabled: boolean) => void; + }; } const defaultAppContext = { @@ -35,6 +40,11 @@ const defaultAppContext = { }, }; +const defaultMarketplaceContext = { + isAutoConnectDisabled: false, + setIsAutoConnectDisabled: () => {}, +}; + const wagmiConfig = createConfig({ chains: [ currentChain ], connectors: [ @@ -49,7 +59,7 @@ const wagmiConfig = createConfig({ }, }); -const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props) => { +const TestApp = ({ children, withSocket, appContext = defaultAppContext, marketplaceContext = defaultMarketplaceContext }: Props) => { const [ queryClient ] = React.useState(() => new QueryClient({ defaultOptions: { queries: { @@ -64,11 +74,13 @@ const TestApp = ({ children, withSocket, appContext = defaultAppContext }: Props - - - { children } - - + + + + { children } + + + diff --git a/public/icons/name.d.ts b/public/icons/name.d.ts index 506817010d..e01deeae1f 100644 --- a/public/icons/name.d.ts +++ b/public/icons/name.d.ts @@ -3,6 +3,7 @@ export type IconName = | "ABI_slim" | "ABI" + | "API_slim" | "API" | "apps_list" | "apps_slim" @@ -48,7 +49,6 @@ | "donate" | "dots" | "edit" - | "email-sent" | "email" | "empty_search_result" | "ENS_slim" @@ -104,6 +104,7 @@ | "output_roots" | "payment_link" | "plus" + | "private_tags_slim" | "privattags" | "profile" | "publictags_slim" @@ -120,6 +121,7 @@ | "score/score-ok" | "search" | "share" + | "sign_out" | "social/canny" | "social/coingecko" | "social/coinmarketcap" @@ -165,6 +167,7 @@ | "validator" | "verification-steps/finalized" | "verification-steps/unfinalized" + | "verified_slim" | "verified" | "wallet" | "wallets/coinbase" diff --git a/theme/components/PinInput.ts b/theme/components/PinInput.ts new file mode 100644 index 0000000000..251b38ca78 --- /dev/null +++ b/theme/components/PinInput.ts @@ -0,0 +1,34 @@ +import { defineStyle, defineStyleConfig } from '@chakra-ui/styled-system'; + +import getOutlinedFieldStyles from '../utils/getOutlinedFieldStyles'; + +const baseStyle = defineStyle({ + textAlign: 'center', + bgColor: 'dialog_bg', +}); + +const sizes = { + md: defineStyle({ + fontSize: 'md', + w: 10, + h: 10, + borderRadius: 'md', + }), +}; + +const variants = { + outline: defineStyle( + (props) => getOutlinedFieldStyles(props), + ), +}; + +const PinInput = defineStyleConfig({ + baseStyle, + sizes, + variants, + defaultProps: { + size: 'md', + }, +}); + +export default PinInput; diff --git a/theme/components/index.ts b/theme/components/index.ts index b32509ccaa..61903f55f6 100644 --- a/theme/components/index.ts +++ b/theme/components/index.ts @@ -10,6 +10,7 @@ import Input from './Input'; import Link from './Link'; import Menu from './Menu'; import Modal from './Modal'; +import PinInput from './PinInput'; import Popover from './Popover'; import Radio from './Radio'; import Select from './Select'; @@ -36,6 +37,7 @@ const components = { Link, Menu, Modal, + PinInput, Popover, Radio, Select, diff --git a/theme/global.ts b/theme/global.ts index e50b7eeaf6..acdbc2fecb 100644 --- a/theme/global.ts +++ b/theme/global.ts @@ -3,6 +3,7 @@ import { mode } from '@chakra-ui/theme-tools'; import scrollbar from './foundations/scrollbar'; import addressEntity from './globals/address-entity'; +import recaptcha from './globals/recaptcha'; import getDefaultTransitionProps from './utils/getDefaultTransitionProps'; const global = (props: StyleFunctionProps) => ({ @@ -25,6 +26,7 @@ const global = (props: StyleFunctionProps) => ({ }, ...scrollbar(props), ...addressEntity(props), + ...recaptcha(), }); export default global; diff --git a/theme/globals/recaptcha.ts b/theme/globals/recaptcha.ts new file mode 100644 index 0000000000..0db7a295a2 --- /dev/null +++ b/theme/globals/recaptcha.ts @@ -0,0 +1,9 @@ +const styles = () => { + return { + '.grecaptcha-badge': { + zIndex: 'toast', + }, + }; +}; + +export default styles; diff --git a/types/api/account.ts b/types/api/account.ts index 67a8c3adef..50af396ea7 100644 --- a/types/api/account.ts +++ b/types/api/account.ts @@ -71,6 +71,7 @@ export interface UserInfo { name?: string; nickname?: string; email: string | null; + address_hash: string | null; avatar?: string; } diff --git a/types/utils.ts b/types/utils.ts index 6cb7a82094..cd48e6ec09 100644 --- a/types/utils.ts +++ b/types/utils.ts @@ -17,3 +17,6 @@ export type PickByType = Record< {[K in keyof T]: T[K] extends X ? K : never}[keyof T], X >; + +// Make some properties of an object optional +export type PartialBy = Omit & Partial> diff --git a/ui/address/contract/audits/ContractSubmitAuditForm.tsx b/ui/address/contract/audits/ContractSubmitAuditForm.tsx index b03d4884cc..d64372a77f 100644 --- a/ui/address/contract/audits/ContractSubmitAuditForm.tsx +++ b/ui/address/contract/audits/ContractSubmitAuditForm.tsx @@ -1,23 +1,18 @@ import { Button, VStack } from '@chakra-ui/react'; import React from 'react'; import type { SubmitHandler } from 'react-hook-form'; -import { useForm } from 'react-hook-form'; +import { FormProvider, useForm } from 'react-hook-form'; import type { SmartContractSecurityAuditSubmission } from 'types/api/contract'; import type { ResourceError } from 'lib/api/resources'; import useApiFetch from 'lib/api/useApiFetch'; +import dayjs from 'lib/date/dayjs'; import useToast from 'lib/hooks/useToast'; - -import AuditComment from './fields/AuditComment'; -import AuditCompanyName from './fields/AuditCompanyName'; -import AuditProjectName from './fields/AuditProjectName'; -import AuditProjectUrl from './fields/AuditProjectUrl'; -import AuditReportDate from './fields/AuditReportDate'; -import AuditReportUrl from './fields/AuditReportUrl'; -import AuditSubmitterEmail from './fields/AuditSubmitterEmail'; -import AuditSubmitterIsOwner from './fields/AuditSubmitterIsOwner'; -import AuditSubmitterName from './fields/AuditSubmitterName'; +import FormFieldCheckbox from 'ui/shared/forms/fields/FormFieldCheckbox'; +import FormFieldEmail from 'ui/shared/forms/fields/FormFieldEmail'; +import FormFieldText from 'ui/shared/forms/fields/FormFieldText'; +import FormFieldUrl from 'ui/shared/forms/fields/FormFieldUrl'; interface Props { address?: string; @@ -46,10 +41,11 @@ const ContractSubmitAuditForm = ({ address, onSuccess }: Props) => { const apiFetch = useApiFetch(); const toast = useToast(); - const { handleSubmit, formState, control, setError } = useForm({ + const formApi = useForm({ mode: 'onTouched', defaultValues: { is_project_owner: false }, }); + const { handleSubmit, formState, setError } = formApi; const onFormSubmit: SubmitHandler = React.useCallback(async(data) => { try { @@ -94,30 +90,46 @@ const ContractSubmitAuditForm = ({ address, onSuccess }: Props) => { }, [ apiFetch, address, toast, setError, onSuccess ]); return ( -
- - - - - - - - - - - - - -
+ + + ); }; diff --git a/ui/address/contract/audits/fields/AuditComment.tsx b/ui/address/contract/audits/fields/AuditComment.tsx deleted file mode 100644 index 918a496dea..0000000000 --- a/ui/address/contract/audits/fields/AuditComment.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { FormControl, Textarea } from '@chakra-ui/react'; -import React from 'react'; -import type { Control, ControllerProps } from 'react-hook-form'; -import { Controller } from 'react-hook-form'; - -import InputPlaceholder from 'ui/shared/InputPlaceholder'; - -import type { Inputs } from '../ContractSubmitAuditForm'; - -interface Props { - control: Control; -} - -const AuditComment = ({ control }: Props) => { - const renderControl: ControllerProps['render'] = React.useCallback(({ field, fieldState }) => { - return ( - -