@rango-dev/widget-embedded 0.28.3-next.0 → 0.28.3-next.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/HeaderButtons/HeaderButtons.d.ts.map +1 -1
- package/dist/components/HeaderButtons/NotificationsBadge.d.ts +3 -0
- package/dist/components/HeaderButtons/NotificationsBadge.d.ts.map +1 -0
- package/dist/components/NotificationContent/NotificationContent.d.ts.map +1 -1
- package/dist/components/NotificationContent/NotificationContent.styles.d.ts +769 -0
- package/dist/components/NotificationContent/NotificationContent.styles.d.ts.map +1 -1
- package/dist/components/SwapDetails/SwapDetails.d.ts.map +1 -1
- package/dist/hooks/useBootstrap/useBootstrap.d.ts.map +1 -1
- package/dist/hooks/useSyncNotifications/index.d.ts +2 -0
- package/dist/hooks/useSyncNotifications/index.d.ts.map +1 -0
- package/dist/hooks/useSyncNotifications/useSyncNotifications.d.ts +2 -0
- package/dist/hooks/useSyncNotifications/useSyncNotifications.d.ts.map +1 -0
- package/dist/hooks/useWalletList.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +4 -4
- package/dist/store/notification.d.ts +27 -7
- package/dist/store/notification.d.ts.map +1 -1
- package/dist/types/notification.d.ts +0 -1
- package/dist/types/notification.d.ts.map +1 -1
- package/dist/utils/swap.d.ts +2 -0
- package/dist/utils/swap.d.ts.map +1 -1
- package/dist/widget-embedded.build.json +1 -0
- package/package.json +2 -2
- package/src/components/HeaderButtons/HeaderButtons.tsx +3 -2
- package/src/components/HeaderButtons/{UnreadNotificationsBadge.tsx → NotificationsBadge.tsx} +3 -3
- package/src/components/NotificationContent/NotificationContent.styles.ts +17 -2
- package/src/components/NotificationContent/NotificationContent.tsx +28 -3
- package/src/components/SwapDetails/SwapDetails.tsx +5 -6
- package/src/containers/WidgetInfo/WidgetInfo.tsx +1 -1
- package/src/hooks/useBootstrap/useBootstrap.ts +2 -0
- package/src/hooks/useSyncNotifications/index.ts +1 -0
- package/src/hooks/useSyncNotifications/useSyncNotifications.ts +25 -0
- package/src/hooks/useWalletList.ts +7 -4
- package/src/store/notification.ts +62 -17
- package/src/types/notification.ts +0 -1
- package/src/utils/swap.ts +12 -0
- package/dist/components/HeaderButtons/UnreadNotificationsBadge.d.ts +0 -3
- package/dist/components/HeaderButtons/UnreadNotificationsBadge.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rango-dev/widget-embedded",
|
|
3
|
-
"version": "0.28.3-next.
|
|
3
|
+
"version": "0.28.3-next.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"source": "./src/index.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@lingui/core": "4.2.1",
|
|
26
26
|
"@lingui/react": "4.2.1",
|
|
27
27
|
"@rango-dev/logging-core": "^0.3.1-next.1",
|
|
28
|
-
"@rango-dev/provider-all": "^0.33.3-next.
|
|
28
|
+
"@rango-dev/provider-all": "^0.33.3-next.1",
|
|
29
29
|
"@rango-dev/queue-manager-core": "^0.26.0",
|
|
30
30
|
"@rango-dev/queue-manager-rango-preset": "^0.33.3-next.0",
|
|
31
31
|
"@rango-dev/queue-manager-react": "^0.26.0",
|
|
@@ -17,8 +17,8 @@ import { NotificationContent } from '../NotificationContent';
|
|
|
17
17
|
|
|
18
18
|
import { HeaderButton } from './HeaderButtons.styles';
|
|
19
19
|
import InProgressTransactionBadge from './InProgressTransactionBadge';
|
|
20
|
+
import { NotificationsBadge } from './NotificationsBadge';
|
|
20
21
|
import { RefreshButton } from './RefreshButton';
|
|
21
|
-
import { UnreadNotificationsBadge } from './UnreadNotificationsBadge';
|
|
22
22
|
|
|
23
23
|
export function HeaderButtons(props: HeaderButtonsPropTypes) {
|
|
24
24
|
const {
|
|
@@ -52,6 +52,7 @@ export function HeaderButtons(props: HeaderButtonsPropTypes) {
|
|
|
52
52
|
<Popover
|
|
53
53
|
align="end"
|
|
54
54
|
alignOffset={-88}
|
|
55
|
+
sideOffset={15}
|
|
55
56
|
collisionPadding={{ right: 20, left: 20 }}
|
|
56
57
|
container={getContainer()}
|
|
57
58
|
content={<NotificationContent />}>
|
|
@@ -62,7 +63,7 @@ export function HeaderButtons(props: HeaderButtonsPropTypes) {
|
|
|
62
63
|
content={i18n.t('Notifications')}>
|
|
63
64
|
<HeaderButton size="small" variant="ghost">
|
|
64
65
|
<NotificationsIcon size={18} color="black" />
|
|
65
|
-
<
|
|
66
|
+
<NotificationsBadge />
|
|
66
67
|
</HeaderButton>
|
|
67
68
|
</Tooltip>
|
|
68
69
|
</div>
|
package/src/components/HeaderButtons/{UnreadNotificationsBadge.tsx → NotificationsBadge.tsx}
RENAMED
|
@@ -6,10 +6,10 @@ import { useNotificationStore } from '../../store/notification';
|
|
|
6
6
|
|
|
7
7
|
import { NotificationsBadgeContainer } from './HeaderButtons.styles';
|
|
8
8
|
|
|
9
|
-
export function
|
|
10
|
-
const {
|
|
9
|
+
export function NotificationsBadge() {
|
|
10
|
+
const { getNotifications } = useNotificationStore();
|
|
11
11
|
|
|
12
|
-
const notificationsList =
|
|
12
|
+
const notificationsList = getNotifications();
|
|
13
13
|
|
|
14
14
|
const notificationsCount = notificationsList.length;
|
|
15
15
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { darkTheme, ListItemButton, styled } from '@rango-dev/ui';
|
|
1
|
+
import { Button, darkTheme, ListItemButton, styled } from '@rango-dev/ui';
|
|
2
2
|
|
|
3
3
|
export const Container = styled('div', {
|
|
4
4
|
padding: '$10',
|
|
@@ -7,6 +7,17 @@ export const Container = styled('div', {
|
|
|
7
7
|
minHeight: '150px',
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
+
export const Header = styled('div', {
|
|
11
|
+
padding: '0 $10',
|
|
12
|
+
display: 'flex',
|
|
13
|
+
justifyContent: 'space-between',
|
|
14
|
+
alignItems: 'center',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const ClearAllButton = styled(Button, {
|
|
18
|
+
padding: '$5 !important',
|
|
19
|
+
});
|
|
20
|
+
|
|
10
21
|
export const List = styled('ul', {
|
|
11
22
|
padding: 0,
|
|
12
23
|
margin: 0,
|
|
@@ -32,7 +43,7 @@ export const ListItem = styled(ListItemButton, {
|
|
|
32
43
|
|
|
33
44
|
export const Images = styled('div', {
|
|
34
45
|
display: 'flex',
|
|
35
|
-
padding: 0,
|
|
46
|
+
padding: '0 0 0 $5',
|
|
36
47
|
alignItems: 'center',
|
|
37
48
|
alignSelf: 'stretch',
|
|
38
49
|
});
|
|
@@ -46,3 +57,7 @@ export const NotFoundContainer = styled('div', {
|
|
|
46
57
|
width: '100%',
|
|
47
58
|
height: '150px',
|
|
48
59
|
});
|
|
60
|
+
|
|
61
|
+
export const IconContainer = styled('span', {
|
|
62
|
+
paddingRight: '$8',
|
|
63
|
+
});
|
|
@@ -17,7 +17,10 @@ import { useNotificationStore } from '../../store/notification';
|
|
|
17
17
|
import { areTokensEqual } from '../../utils/wallets';
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
|
+
ClearAllButton,
|
|
20
21
|
Container,
|
|
22
|
+
Header,
|
|
23
|
+
IconContainer,
|
|
21
24
|
Images,
|
|
22
25
|
List,
|
|
23
26
|
ListItem,
|
|
@@ -29,9 +32,9 @@ const MAX_NOTIFICATIONS_DISPLAYED = 4;
|
|
|
29
32
|
export function NotificationContent() {
|
|
30
33
|
const navigate = useNavigate();
|
|
31
34
|
|
|
32
|
-
const {
|
|
35
|
+
const { getNotifications, clearNotifications } = useNotificationStore();
|
|
33
36
|
|
|
34
|
-
const notifications: Notification[] =
|
|
37
|
+
const notifications: Notification[] = getNotifications();
|
|
35
38
|
const blockchains = useAppStore().blockchains();
|
|
36
39
|
const tokens = useAppStore().tokens();
|
|
37
40
|
const sortedNotification = notifications
|
|
@@ -44,6 +47,24 @@ export function NotificationContent() {
|
|
|
44
47
|
|
|
45
48
|
return (
|
|
46
49
|
<Container>
|
|
50
|
+
{sortedNotification.length > 0 && (
|
|
51
|
+
<>
|
|
52
|
+
<Header>
|
|
53
|
+
<Typography variant="label" size="medium">
|
|
54
|
+
{i18n.t('Notifications')}
|
|
55
|
+
</Typography>
|
|
56
|
+
<ClearAllButton
|
|
57
|
+
variant="ghost"
|
|
58
|
+
size="xsmall"
|
|
59
|
+
onClick={clearNotifications}>
|
|
60
|
+
<Typography variant="body" size="xsmall">
|
|
61
|
+
{i18n.t('Clear all')}
|
|
62
|
+
</Typography>
|
|
63
|
+
</ClearAllButton>
|
|
64
|
+
</Header>
|
|
65
|
+
<Divider direction="vertical" size={4} />
|
|
66
|
+
</>
|
|
67
|
+
)}
|
|
47
68
|
{sortedNotification.length ? (
|
|
48
69
|
<List>
|
|
49
70
|
{sortedNotification.map((notificationItem, index) => {
|
|
@@ -103,7 +124,11 @@ export function NotificationContent() {
|
|
|
103
124
|
</div>
|
|
104
125
|
</Images>
|
|
105
126
|
}
|
|
106
|
-
end={
|
|
127
|
+
end={
|
|
128
|
+
<IconContainer>
|
|
129
|
+
<ChevronRightIcon size={12} color="gray" />
|
|
130
|
+
</IconContainer>
|
|
131
|
+
}
|
|
107
132
|
/>
|
|
108
133
|
</React.Fragment>
|
|
109
134
|
);
|
|
@@ -96,21 +96,20 @@ export function SwapDetails(props: SwapDetailsProps) {
|
|
|
96
96
|
setModalState(null);
|
|
97
97
|
};
|
|
98
98
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
const unreadNotifications = getUnreadNotifications();
|
|
99
|
+
const getNotifications = useNotificationStore.use.getNotifications();
|
|
100
|
+
const removeNotification = useNotificationStore.use.removeNotification();
|
|
101
|
+
const notifications = getNotifications();
|
|
103
102
|
const currentStep = getCurrentStep(swap);
|
|
104
103
|
const currentStepNetworkStatus = currentStep?.networkStatus;
|
|
105
104
|
|
|
106
105
|
useEffect(() => {
|
|
107
|
-
const existNotification =
|
|
106
|
+
const existNotification = notifications.find(
|
|
108
107
|
(n) => n.requestId === swap.requestId
|
|
109
108
|
);
|
|
110
109
|
if (existNotification) {
|
|
111
110
|
if (swap.status === 'success' || swap.status === 'failed') {
|
|
112
111
|
setShowCompletedModal(swap.status);
|
|
113
|
-
|
|
112
|
+
removeNotification(swap.requestId);
|
|
114
113
|
} else if (showCompletedModal) {
|
|
115
114
|
setShowCompletedModal(null);
|
|
116
115
|
}
|
|
@@ -31,7 +31,7 @@ export function WidgetInfo(props: React.PropsWithChildren) {
|
|
|
31
31
|
const swappers = useAppStore().swappers();
|
|
32
32
|
const loadingStatus = useAppStore().fetchStatus;
|
|
33
33
|
const resetLanguage = useLanguage().resetLanguage;
|
|
34
|
-
const notifications = useNotificationStore().
|
|
34
|
+
const notifications = useNotificationStore().getNotifications();
|
|
35
35
|
|
|
36
36
|
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
|
37
37
|
const value: WidgetInfoContextInterface = {
|
|
@@ -14,11 +14,13 @@ import { tabManager } from '../../store/ui';
|
|
|
14
14
|
import { useFetchApiConfig } from '../useFetchApiConfig';
|
|
15
15
|
import { useForceAutoConnect } from '../useForceAutoConnect';
|
|
16
16
|
import { useSubscribeToWidgetEvents } from '../useSubscribeToWidgetEvents';
|
|
17
|
+
import { useSyncNotifications } from '../useSyncNotifications';
|
|
17
18
|
|
|
18
19
|
export function useBootstrap() {
|
|
19
20
|
useForceAutoConnect();
|
|
20
21
|
globalFont();
|
|
21
22
|
useSubscribeToWidgetEvents();
|
|
23
|
+
useSyncNotifications();
|
|
22
24
|
const blockchains = useAppStore().blockchains();
|
|
23
25
|
const { canSwitchNetworkTo } = useWallets();
|
|
24
26
|
const [lastConnectedWalletWithNetwork, setLastConnectedWalletWithNetwork] =
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useSyncNotifications } from './useSyncNotifications';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useManager } from '@rango-dev/queue-manager-react';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
import { useNotificationStore } from '../../store/notification';
|
|
5
|
+
import { getPendingSwaps } from '../../utils/queue';
|
|
6
|
+
|
|
7
|
+
export function useSyncNotifications() {
|
|
8
|
+
const { isSynced, syncNotifications } = useNotificationStore();
|
|
9
|
+
const { manager, state } = useManager();
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const shouldSyncNotifications =
|
|
13
|
+
useNotificationStore.persist.hasHydrated() &&
|
|
14
|
+
state.loadedFromPersistor &&
|
|
15
|
+
!isSynced;
|
|
16
|
+
|
|
17
|
+
if (shouldSyncNotifications) {
|
|
18
|
+
syncNotifications(getPendingSwaps(manager));
|
|
19
|
+
}
|
|
20
|
+
}, [
|
|
21
|
+
useNotificationStore.persist.hasHydrated(),
|
|
22
|
+
state.loadedFromPersistor,
|
|
23
|
+
isSynced,
|
|
24
|
+
]);
|
|
25
|
+
}
|
|
@@ -56,7 +56,8 @@ export function useWalletList(params: Params) {
|
|
|
56
56
|
|
|
57
57
|
wallets = detectMobileScreens()
|
|
58
58
|
? wallets.filter(
|
|
59
|
-
(wallet) =>
|
|
59
|
+
(wallet) =>
|
|
60
|
+
wallet.showOnMobile !== false && state(wallet.type).installed
|
|
60
61
|
)
|
|
61
62
|
: wallets;
|
|
62
63
|
|
|
@@ -137,9 +138,11 @@ export function useWalletList(params: Params) {
|
|
|
137
138
|
const isEvmWalletInstalledExceptDefault = wallets.filter(
|
|
138
139
|
(wallet) =>
|
|
139
140
|
wallet.state != WalletState.NOT_INSTALLED &&
|
|
140
|
-
![
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
![
|
|
142
|
+
WalletTypes.DEFAULT,
|
|
143
|
+
WalletTypes.WALLET_CONNECT_2,
|
|
144
|
+
WalletTypes.LEDGER,
|
|
145
|
+
].includes(wallet.type as WalletTypes) &&
|
|
143
146
|
getWalletInfo(wallet.type).supportedChains.filter(
|
|
144
147
|
(blockchain) => blockchain.type == 'EVM'
|
|
145
148
|
).length > 0
|
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
import type { Notification } from '../types/notification';
|
|
2
|
-
import type {
|
|
3
|
-
Route,
|
|
4
|
-
RouteEvent,
|
|
5
|
-
StepEvent,
|
|
6
|
-
} from '@rango-dev/queue-manager-rango-preset';
|
|
7
2
|
|
|
3
|
+
import {
|
|
4
|
+
type PendingSwapWithQueueID,
|
|
5
|
+
type Route,
|
|
6
|
+
type RouteEvent,
|
|
7
|
+
type StepEvent,
|
|
8
|
+
} from '@rango-dev/queue-manager-rango-preset';
|
|
8
9
|
import { create } from 'zustand';
|
|
9
10
|
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
|
10
11
|
|
|
11
12
|
import createSelectors from './selectors';
|
|
12
13
|
|
|
13
14
|
export interface NotificationState {
|
|
15
|
+
isSynced: boolean;
|
|
14
16
|
notifications: Notification[];
|
|
15
17
|
setNotification: (event: RouteEvent | StepEvent, route: Route) => void;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
removeNotification: (requestId: Notification['requestId']) => void;
|
|
19
|
+
getNotifications: () => Notification[];
|
|
20
|
+
syncNotifications: (swaps: PendingSwapWithQueueID[]) => void;
|
|
21
|
+
clearNotifications: () => void;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
export const useNotificationStore = createSelectors(
|
|
21
25
|
create<NotificationState>()(
|
|
22
26
|
persist(
|
|
23
27
|
subscribeWithSelector((set, get) => ({
|
|
28
|
+
isSynced: false,
|
|
24
29
|
notifications: [],
|
|
25
30
|
setNotification: (event, route) => {
|
|
26
31
|
const fromStep = route.steps[0];
|
|
@@ -29,7 +34,6 @@ export const useNotificationStore = createSelectors(
|
|
|
29
34
|
const notification: Notification = {
|
|
30
35
|
event,
|
|
31
36
|
creationTime: Date.now(),
|
|
32
|
-
read: false,
|
|
33
37
|
requestId: route.requestId,
|
|
34
38
|
route: {
|
|
35
39
|
from: {
|
|
@@ -53,24 +57,65 @@ export const useNotificationStore = createSelectors(
|
|
|
53
57
|
notifications: [...excludedList, notification],
|
|
54
58
|
}));
|
|
55
59
|
},
|
|
56
|
-
|
|
60
|
+
removeNotification: (requestId) => {
|
|
57
61
|
set((state) => ({
|
|
58
|
-
notifications: state.notifications.
|
|
59
|
-
|
|
60
|
-
? { ...notificationItem, read: true }
|
|
61
|
-
: notificationItem
|
|
62
|
+
notifications: state.notifications.filter(
|
|
63
|
+
(notification) => notification.requestId !== requestId
|
|
62
64
|
),
|
|
63
65
|
}));
|
|
64
66
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
getNotifications: () => {
|
|
68
|
+
const { isSynced, notifications } = get();
|
|
69
|
+
return isSynced ? notifications : [];
|
|
70
|
+
},
|
|
71
|
+
syncNotifications: (swaps) => {
|
|
72
|
+
const { isSynced, notifications } = get();
|
|
73
|
+
|
|
74
|
+
if (!isSynced) {
|
|
75
|
+
const nextNotifications: Notification[] = [];
|
|
76
|
+
notifications.forEach((notification) => {
|
|
77
|
+
const swapExist = swaps.some(
|
|
78
|
+
(swap) => swap.swap.requestId === notification.requestId
|
|
79
|
+
);
|
|
80
|
+
if (swapExist) {
|
|
81
|
+
nextNotifications.push(notification);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
set({ isSynced: true, notifications: nextNotifications });
|
|
86
|
+
}
|
|
69
87
|
},
|
|
88
|
+
clearNotifications: () => set({ notifications: [] }),
|
|
70
89
|
})),
|
|
90
|
+
|
|
71
91
|
{
|
|
72
92
|
name: 'notification',
|
|
73
93
|
skipHydration: true,
|
|
94
|
+
partialize: ({ isSynced, ...otherProps }) => otherProps,
|
|
95
|
+
version: 1,
|
|
96
|
+
migrate: (persistedState, version) => {
|
|
97
|
+
if (version === 0) {
|
|
98
|
+
/**
|
|
99
|
+
* Eliminate the persistence of read notifications,
|
|
100
|
+
* and remove the "read" flag from other notifications to align with the current modifications in the notification store structure.
|
|
101
|
+
*/
|
|
102
|
+
const oldNotifications = (
|
|
103
|
+
persistedState as {
|
|
104
|
+
notifications: (Notification & { read: boolean })[];
|
|
105
|
+
}
|
|
106
|
+
).notifications;
|
|
107
|
+
|
|
108
|
+
(persistedState as NotificationState).notifications =
|
|
109
|
+
oldNotifications
|
|
110
|
+
.filter(
|
|
111
|
+
(notification: Notification & { read: boolean }) =>
|
|
112
|
+
!notification.read
|
|
113
|
+
)
|
|
114
|
+
.map(({ read, ...otherProps }) => otherProps);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return persistedState as NotificationState;
|
|
118
|
+
},
|
|
74
119
|
}
|
|
75
120
|
)
|
|
76
121
|
)
|
package/src/utils/swap.ts
CHANGED
|
@@ -23,6 +23,11 @@ import type {
|
|
|
23
23
|
import type { PendingSwap, PendingSwapStep } from 'rango-types';
|
|
24
24
|
|
|
25
25
|
import { i18n } from '@lingui/core';
|
|
26
|
+
import {
|
|
27
|
+
type RouteEvent,
|
|
28
|
+
RouteEventType,
|
|
29
|
+
type StepEvent,
|
|
30
|
+
} from '@rango-dev/queue-manager-rango-preset';
|
|
26
31
|
import BigNumber from 'bignumber.js';
|
|
27
32
|
import { PendingSwapNetworkStatus } from 'rango-types';
|
|
28
33
|
|
|
@@ -782,3 +787,10 @@ export function isTokensIdentical(tokenA: Token, tokenB: Token) {
|
|
|
782
787
|
tokenA.address === tokenB.address
|
|
783
788
|
);
|
|
784
789
|
}
|
|
790
|
+
|
|
791
|
+
export function isSwapFinished(event: RouteEvent | StepEvent) {
|
|
792
|
+
return (
|
|
793
|
+
event.type === RouteEventType.FAILED ||
|
|
794
|
+
event.type === RouteEventType.SUCCEEDED
|
|
795
|
+
);
|
|
796
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"UnreadNotificationsBadge.d.ts","sourceRoot":"","sources":["../../../src/components/HeaderButtons/UnreadNotificationsBadge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,wBAAgB,wBAAwB,6BAmBvC"}
|