Commit f8767a3e authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into release/v2-0-0

parents 97fde968 bb0b5f12
...@@ -12,6 +12,7 @@ import { Tooltip } from 'toolkit/chakra/tooltip'; ...@@ -12,6 +12,7 @@ import { Tooltip } from 'toolkit/chakra/tooltip';
import { useDisclosure } from 'toolkit/hooks/useDisclosure'; import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import IconSvg from 'ui/shared/IconSvg'; import IconSvg from 'ui/shared/IconSvg';
import AuthGuard from 'ui/snippets/auth/AuthGuard'; import AuthGuard from 'ui/snippets/auth/AuthGuard';
import useProfileQuery from 'ui/snippets/auth/useProfileQuery';
import WatchlistAddModal from 'ui/watchlist/AddressModal/AddressModal'; import WatchlistAddModal from 'ui/watchlist/AddressModal/AddressModal';
import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal'; import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal';
...@@ -27,6 +28,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { ...@@ -27,6 +28,7 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const onFocusCapture = usePreventFocusAfterModalClosing(); const onFocusCapture = usePreventFocusAfterModalClosing();
const profileQuery = useProfileQuery();
const handleAddToFavorite = React.useCallback(() => { const handleAddToFavorite = React.useCallback(() => {
watchListId ? deleteModalProps.onOpen() : addModalProps.onOpen(); watchListId ? deleteModalProps.onOpen() : addModalProps.onOpen();
...@@ -78,6 +80,8 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => { ...@@ -78,6 +80,8 @@ const AddressFavoriteButton = ({ className, hash, watchListId }: Props) => {
isAdd isAdd
onSuccess={ handleAddOrDeleteSuccess } onSuccess={ handleAddOrDeleteSuccess }
data={ formData } data={ formData }
hasEmail={ Boolean(profileQuery.data?.email) }
showEmailAlert
/> />
{ formData.id && ( { formData.id && (
<DeleteAddressModal <DeleteAddressModal
......
...@@ -34,13 +34,10 @@ test.beforeEach(async({ mockApiResponse, mockTextAd }) => { ...@@ -34,13 +34,10 @@ test.beforeEach(async({ mockApiResponse, mockTextAd }) => {
}); });
test('base view', async({ render, page, createSocket }) => { test('base view', async({ render, page, createSocket }) => {
test.slow();
const component = await render(<Token/>, { hooksConfig }, { withSocket: true }); const component = await render(<Token/>, { hooksConfig }, { withSocket: true });
const socket = await createSocket(); const socket = await createSocket();
const channel = await socketServer.joinChannel(socket, `tokens:${ hash }`); await socketServer.joinChannel(socket, `tokens:${ hash }`);
socketServer.sendMessage(socket, channel, 'total_supply', { total_supply: 10 ** 20 });
await component.getByText('100 ARIA').waitFor({ state: 'visible', timeout: 10_000 });
await expect(component).toHaveScreenshot({ await expect(component).toHaveScreenshot({
mask: [ page.locator(pwConfig.adsBannerSelector) ], mask: [ page.locator(pwConfig.adsBannerSelector) ],
......
...@@ -20,6 +20,7 @@ import useProfileQuery from 'ui/snippets/auth/useProfileQuery'; ...@@ -20,6 +20,7 @@ import useProfileQuery from 'ui/snippets/auth/useProfileQuery';
import useRedirectForInvalidAuthToken from 'ui/snippets/auth/useRedirectForInvalidAuthToken'; import useRedirectForInvalidAuthToken from 'ui/snippets/auth/useRedirectForInvalidAuthToken';
import AddressModal from 'ui/watchlist/AddressModal/AddressModal'; import AddressModal from 'ui/watchlist/AddressModal/AddressModal';
import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal'; import DeleteAddressModal from 'ui/watchlist/DeleteAddressModal';
import WatchlistEmailAlert from 'ui/watchlist/WatchlistEmailAlert';
import WatchListItem from 'ui/watchlist/WatchlistTable/WatchListItem'; import WatchListItem from 'ui/watchlist/WatchlistTable/WatchListItem';
import WatchlistTable from 'ui/watchlist/WatchlistTable/WatchlistTable'; import WatchlistTable from 'ui/watchlist/WatchlistTable/WatchlistTable';
...@@ -41,6 +42,8 @@ const WatchList: React.FC = () => { ...@@ -41,6 +42,8 @@ const WatchList: React.FC = () => {
const [ addressModalData, setAddressModalData ] = useState<WatchlistAddress>(); const [ addressModalData, setAddressModalData ] = useState<WatchlistAddress>();
const [ deleteModalData, setDeleteModalData ] = useState<WatchlistAddress>(); const [ deleteModalData, setDeleteModalData ] = useState<WatchlistAddress>();
const hasEmail = Boolean(profileQuery.data?.email);
const onEditClick = useCallback((data: WatchlistAddress) => { const onEditClick = useCallback((data: WatchlistAddress) => {
setAddressModalData(data); setAddressModalData(data);
addressModalProps.onOpen(); addressModalProps.onOpen();
...@@ -75,12 +78,6 @@ const WatchList: React.FC = () => { ...@@ -75,12 +78,6 @@ const WatchList: React.FC = () => {
); );
}, [ deleteModalData?.id, queryClient ]); }, [ deleteModalData?.id, queryClient ]);
const description = (
<AccountPageDescription>
An email notification can be sent to you when an address on your watch list sends or receives any transactions.
</AccountPageDescription>
);
const content = (() => { const content = (() => {
const actionBar = pagination.isVisible ? ( const actionBar = pagination.isVisible ? (
<ActionBar mt={ -6 }> <ActionBar mt={ -6 }>
...@@ -90,7 +87,10 @@ const WatchList: React.FC = () => { ...@@ -90,7 +87,10 @@ const WatchList: React.FC = () => {
return ( return (
<> <>
{ description } { !hasEmail && <WatchlistEmailAlert/> }
<AccountPageDescription>
An email notification can be sent to you when an address on your watch list sends or receives any transactions.
</AccountPageDescription>
<DataListDisplay <DataListDisplay
isError={ isError } isError={ isError }
itemsNum={ data?.items.length } itemsNum={ data?.items.length }
...@@ -105,7 +105,7 @@ const WatchList: React.FC = () => { ...@@ -105,7 +105,7 @@ const WatchList: React.FC = () => {
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
onDeleteClick={ onDeleteClick } onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick } onEditClick={ onEditClick }
hasEmail={ Boolean(profileQuery.data?.email) } hasEmail={ hasEmail }
/> />
)) } )) }
</Box> </Box>
...@@ -116,7 +116,7 @@ const WatchList: React.FC = () => { ...@@ -116,7 +116,7 @@ const WatchList: React.FC = () => {
onDeleteClick={ onDeleteClick } onDeleteClick={ onDeleteClick }
onEditClick={ onEditClick } onEditClick={ onEditClick }
top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 } top={ pagination.isVisible ? ACTION_BAR_HEIGHT_DESKTOP : 0 }
hasEmail={ Boolean(profileQuery.data?.email) } hasEmail={ hasEmail }
/> />
</Box> </Box>
</DataListDisplay> </DataListDisplay>
...@@ -133,6 +133,7 @@ const WatchList: React.FC = () => { ...@@ -133,6 +133,7 @@ const WatchList: React.FC = () => {
onSuccess={ onAddOrEditSuccess } onSuccess={ onAddOrEditSuccess }
data={ addressModalData } data={ addressModalData }
isAdd={ !addressModalData } isAdd={ !addressModalData }
hasEmail={ hasEmail }
/> />
{ deleteModalData && ( { deleteModalData && (
<DeleteAddressModal <DeleteAddressModal
......
import type { DialogRootProps } from '@chakra-ui/react';
import { Box, Text } from '@chakra-ui/react'; import { Box, Text } from '@chakra-ui/react';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { DialogBody, DialogContent, DialogHeader, DialogRoot } from 'toolkit/chakra/dialog'; import { DialogBody, DialogContent, DialogHeader, DialogRoot } from 'toolkit/chakra/dialog';
import FormSubmitAlert from 'ui/shared/FormSubmitAlert'; import FormSubmitAlert from 'ui/shared/FormSubmitAlert';
interface Props<TData> { interface Props<TData> extends Omit<DialogRootProps, 'children'> {
open: boolean;
onOpenChange: ({ open }: { open: boolean }) => void;
data?: TData; data?: TData;
title: string; title: string;
text?: string; text?: string;
...@@ -23,15 +22,16 @@ export default function FormModal<TData>({ ...@@ -23,15 +22,16 @@ export default function FormModal<TData>({
renderForm, renderForm,
isAlertVisible, isAlertVisible,
setAlertVisible, setAlertVisible,
...rest
}: Props<TData>) { }: Props<TData>) {
const handleOpenChange = useCallback(({ open }: { open: boolean }) => { const handleOpenChange = useCallback(({ open }: { open: boolean }) => {
!open && setAlertVisible?.(false); !open && setAlertVisible?.(false);
onOpenChange({ open }); onOpenChange?.({ open });
}, [ onOpenChange, setAlertVisible ]); }, [ onOpenChange, setAlertVisible ]);
return ( return (
<DialogRoot open={ open } onOpenChange={ handleOpenChange } size={{ lgDown: 'full', lg: 'md' }}> <DialogRoot open={ open } onOpenChange={ handleOpenChange } size={{ lgDown: 'full', lg: 'md' }} { ...rest }>
<DialogContent> <DialogContent>
<DialogHeader>{ title }</DialogHeader> <DialogHeader>{ title }</DialogHeader>
<DialogBody> <DialogBody>
......
...@@ -14,9 +14,6 @@ import { Button } from 'toolkit/chakra/button'; ...@@ -14,9 +14,6 @@ import { Button } from 'toolkit/chakra/button';
import { FormFieldAddress } from 'toolkit/components/forms/fields/FormFieldAddress'; import { FormFieldAddress } from 'toolkit/components/forms/fields/FormFieldAddress';
import { FormFieldCheckbox } from 'toolkit/components/forms/fields/FormFieldCheckbox'; import { FormFieldCheckbox } from 'toolkit/components/forms/fields/FormFieldCheckbox';
import { FormFieldText } from 'toolkit/components/forms/fields/FormFieldText'; import { FormFieldText } from 'toolkit/components/forms/fields/FormFieldText';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import AuthModal from 'ui/snippets/auth/AuthModal';
import useProfileQuery from 'ui/snippets/auth/useProfileQuery';
import AddressFormNotifications from './AddressFormNotifications'; import AddressFormNotifications from './AddressFormNotifications';
...@@ -30,6 +27,8 @@ type Props = { ...@@ -30,6 +27,8 @@ type Props = {
onSuccess: () => Promise<void>; onSuccess: () => Promise<void>;
setAlertVisible: (isAlertVisible: boolean) => void; setAlertVisible: (isAlertVisible: boolean) => void;
isAdd: boolean; isAdd: boolean;
hasEmail: boolean;
showEmailAlert?: boolean;
}; };
export type Inputs = { export type Inputs = {
...@@ -56,16 +55,12 @@ export type Inputs = { ...@@ -56,16 +55,12 @@ export type Inputs = {
}; };
}; };
const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd }) => { const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd, hasEmail, showEmailAlert }) => {
const [ pending, setPending ] = useState(false); const [ pending, setPending ] = useState(false);
const profileQuery = useProfileQuery();
const userWithoutEmail = profileQuery.data && !profileQuery.data.email;
const authModal = useDisclosure();
let notificationsDefault = {} as Inputs['notification_settings']; let notificationsDefault = {} as Inputs['notification_settings'];
if (!data?.notification_settings) { if (!data?.notification_settings) {
NOTIFICATIONS.forEach(n => notificationsDefault[n] = { incoming: !userWithoutEmail, outcoming: !userWithoutEmail }); NOTIFICATIONS.forEach(n => notificationsDefault[n] = { incoming: hasEmail, outcoming: hasEmail });
} else { } else {
notificationsDefault = data.notification_settings; notificationsDefault = data.notification_settings;
} }
...@@ -74,7 +69,7 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd ...@@ -74,7 +69,7 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
defaultValues: { defaultValues: {
address: data?.address_hash || '', address: data?.address_hash || '',
tag: data?.name || '', tag: data?.name || '',
notification: data?.notification_methods ? data.notification_methods.email : !userWithoutEmail, notification: data?.notification_methods ? data.notification_methods.email : hasEmail,
notification_settings: notificationsDefault, notification_settings: notificationsDefault,
}, },
mode: 'onTouched', mode: 'onTouched',
...@@ -149,23 +144,7 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd ...@@ -149,23 +144,7 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
bgColor="dialog.bg" bgColor="dialog.bg"
mb={ 8 } mb={ 8 }
/> />
{ userWithoutEmail ? ( { hasEmail ? (
<>
<Alert
status="info"
display="flex"
flexDirection={{ base: 'column', md: 'row' }}
alignItems={{ base: 'flex-start', lg: 'center' }}
columnGap={ 2 }
rowGap={ 2 }
w="fit-content"
>
To receive notifications you need to add an email to your profile.
<Button variant="outline" size="sm" onClick={ authModal.onOpen }>Add email</Button>
</Alert>
{ authModal.open && <AuthModal initialScreen={{ type: 'email', isAuth: true }} onClose={ authModal.onClose }/> }
</>
) : (
<> <>
<Text color="text.secondary" fontSize="sm" marginBottom={ 5 }> <Text color="text.secondary" fontSize="sm" marginBottom={ 5 }>
Please select what types of notifications you will receive Please select what types of notifications you will receive
...@@ -179,7 +158,17 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd ...@@ -179,7 +158,17 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
label="Email notifications" label="Email notifications"
/> />
</> </>
) } ) : null }
{ !hasEmail && showEmailAlert ? (
<Alert
status="info"
descriptionProps={{ alignItems: 'center', gap: 2 }}
w="fit-content"
mb={ 6 }
>
To receive notifications you need to add an email to your profile.
</Alert>
) : null }
<Button <Button
type="submit" type="submit"
loading={ pending } loading={ pending }
...@@ -190,6 +179,7 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd ...@@ -190,6 +179,7 @@ const AddressForm: React.FC<Props> = ({ data, onSuccess, setAlertVisible, isAdd
</Button> </Button>
</form> </form>
</FormProvider> </FormProvider>
); );
}; };
......
...@@ -12,17 +12,28 @@ type Props = { ...@@ -12,17 +12,28 @@ type Props = {
onOpenChange: ({ open }: { open: boolean }) => void; onOpenChange: ({ open }: { open: boolean }) => void;
onSuccess: () => Promise<void>; onSuccess: () => Promise<void>;
data?: Partial<WatchlistAddress>; data?: Partial<WatchlistAddress>;
hasEmail: boolean;
showEmailAlert?: boolean;
}; };
const AddressModal: React.FC<Props> = ({ open, onOpenChange, onSuccess, data, isAdd }) => { const AddressModal: React.FC<Props> = ({ open, onOpenChange, onSuccess, data, isAdd, hasEmail, showEmailAlert }) => {
const title = !isAdd ? 'Edit watch list address' : 'New address to watch list'; const title = !isAdd ? 'Edit watch list address' : 'New address to watch list';
const text = isAdd ? 'An email notification can be sent to you when an address on your watch list sends or receives any transactions.' : ''; const text = isAdd ? 'An email notification can be sent to you when an address on your watch list sends or receives any transactions.' : '';
const [ isAlertVisible, setAlertVisible ] = useState(false); const [ isAlertVisible, setAlertVisible ] = useState(false);
const renderForm = useCallback(() => { const renderForm = useCallback(() => {
return <AddressForm data={ data } onSuccess={ onSuccess } setAlertVisible={ setAlertVisible } isAdd={ isAdd }/>; return (
}, [ data, isAdd, onSuccess ]); <AddressForm
data={ data }
onSuccess={ onSuccess }
setAlertVisible={ setAlertVisible }
isAdd={ isAdd }
hasEmail={ hasEmail }
showEmailAlert={ showEmailAlert }
/>
);
}, [ data, isAdd, onSuccess, hasEmail, showEmailAlert ]);
return ( return (
<FormModal<WatchlistAddress> <FormModal<WatchlistAddress>
......
import React from 'react';
import { Alert } from 'toolkit/chakra/alert';
import { Button } from 'toolkit/chakra/button';
import { useDisclosure } from 'toolkit/hooks/useDisclosure';
import AuthModal from 'ui/snippets/auth/AuthModal';
const WatchlistEmailAlert = () => {
const authModal = useDisclosure();
return (
<>
<Alert
status="info"
descriptionProps={{ alignItems: 'center', gap: 2 }}
w="fit-content"
mb={ 6 }
>
To receive notifications you need to add an email to your profile.
<Button variant="outline" size="sm" onClick={ authModal.onOpen }>Add email</Button>
</Alert>
{ authModal.open && <AuthModal initialScreen={{ type: 'email', isAuth: true }} onClose={ authModal.onClose }/> }
</>
);
};
export default React.memo(WatchlistEmailAlert);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment