Skip to content

Commit

Permalink
feat: widget events for send to wallet (#309)
Browse files Browse the repository at this point in the history
Co-authored-by: Eugene Chybisov <[email protected]>
  • Loading branch information
DNR500 and chybisov authored Oct 17, 2024
1 parent cf48cf2 commit b247afb
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 40 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { useWidgetEvents, WidgetEvent } from '@lifi/widget';
import { useEffect, useState } from 'react';
import { useDevView } from '../../../hooks';
import { setQueryStringParam } from '../../../utils/setQueryStringParam';
import { CardRowContainer, ExpandableCard } from '../../Card';
import { Switch } from '../../Switch';
import {
CapitalizeFirstLetter,
ControlContainer,
ControlRowContainer,
} from './DesignControls.style';

const initialiseStateFromWidgetEvents = (
widgetEventsMap: Record<string, string>,
allEventsOn: boolean = false,
) =>
Object.values(widgetEventsMap).reduce((accum, eventName) => {
return {
...accum,
[eventName]: allEventsOn,
};
}, {});

export const WidgetEventControls = () => {
const { isDevView } = useDevView();
const widgetEvents = useWidgetEvents();

const { allWidgetEventsOn, setAllWidgetEventsOnForPageLoad } =
useWidgetEventsSearchParam();
const [monitoredEvents, setMonitoredEvents] = useState<
Record<string, boolean>
>(initialiseStateFromWidgetEvents(WidgetEvent, allWidgetEventsOn));

useEffect(() => {
const logFunction = (eventName: string) => (value: any) =>
// eslint-disable-next-line no-console
console.info(eventName, value);

const logFunctionLookUp: Record<string, (value: any) => void> = {};

Object.keys(monitoredEvents).forEach((eventName) => {
const eventListeningOn = monitoredEvents[eventName];
if (eventListeningOn) {
logFunctionLookUp[eventName] = logFunction(eventName);
widgetEvents.on(eventName, logFunctionLookUp[eventName]);
}
});

return () => {
Object.keys(monitoredEvents).forEach((eventName) => {
const eventListeningOn = monitoredEvents[eventName];
if (eventListeningOn) {
widgetEvents.off(eventName, logFunctionLookUp[eventName]);
delete logFunctionLookUp[eventName];
}
});
};
}, [widgetEvents, monitoredEvents]);

const handleAllEventsChange = () => {
const areAllEventsOn = !allWidgetEventsOn;

setAllWidgetEventsOnForPageLoad(areAllEventsOn);

setMonitoredEvents(
initialiseStateFromWidgetEvents(WidgetEvent, areAllEventsOn),
);
};

const handleEventChange = (eventName: string) => {
const newEventsMap = {
...monitoredEvents,
[eventName]: !monitoredEvents[eventName],
};

setMonitoredEvents(newEventsMap);

const areAllEventsOn = Object.values(newEventsMap).every(
(eventOn) => eventOn,
);
setAllWidgetEventsOnForPageLoad(areAllEventsOn);
};

return isDevView ? (
<ExpandableCard title={'Widget Events'} value={''}>
<CardRowContainer
sx={{ paddingBottom: 1, paddingLeft: 1, paddingTop: 0 }}
>
<CapitalizeFirstLetter variant="caption">
Output for events can be viewed in the console when event listeners
are turned on
</CapitalizeFirstLetter>
</CardRowContainer>
<ControlContainer
sx={{
marginLeft: 1,
marginRight: 1,
paddingLeft: 1,
paddingRight: 1,
minHeight: 48,
}}
>
<ControlRowContainer sx={{ padding: 0 }}>
All events on page load
<Switch
checked={allWidgetEventsOn}
onChange={handleAllEventsChange}
aria-label="Toggle All Widget Events"
/>
</ControlRowContainer>
</ControlContainer>
{Object.values(WidgetEvent).map((eventName, i, arr) => (
<CardRowContainer
sx={{ paddingBottom: i < arr.length - 1 ? 0 : 2 }}
key={eventName}
>
{eventName}
<Switch
checked={monitoredEvents[eventName]}
onChange={() => handleEventChange(eventName)}
aria-label={`Enable logging of ${eventName}`}
/>
</CardRowContainer>
))}
</ExpandableCard>
) : null;
};

const getAllWidgetEventsOnFromQueryString = () => {
if (typeof window !== 'undefined') {
const urlParams = new URLSearchParams(window.location.search);
return !!urlParams.get('allWidgetEvents') || false;
}
return false;
};

const useWidgetEventsSearchParam = () => {
const [allWidgetEventsOn, setAllWidgetEventsOn] = useState(
getAllWidgetEventsOnFromQueryString(),
);

const setAllWidgetEventsOnForPageLoad = (on: boolean) => {
setQueryStringParam('allWidgetEvents', on);

setAllWidgetEventsOn(on);
};

return {
allWidgetEventsOn,
setAllWidgetEventsOnForPageLoad,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
WalletManagementControl,
} from './DesignControls';
import { FormValuesControl } from './DesignControls/FormValuesControls';
import { WidgetEventControls } from './DesignControls/WidgetEventsControls';
import {
Drawer,
DrawerContentContainer,
Expand Down Expand Up @@ -124,6 +125,7 @@ export const DrawerControls = () => {
<CardRadiusControl />
<ButtonRadiusControl />
<FormValuesControl />
<WidgetEventControls />
<WalletManagementControl />
<SkeletonControl />
<LayoutControls />
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion packages/widget-playground/src/components/Widget/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './ConnectWalletButton';
export * from './WidgetSkeleton';
export * from './WidgetView';
export * from './WidgetView.style';
export * from './WidgetViewContainer';
13 changes: 2 additions & 11 deletions packages/widget-playground/src/hooks/useDevView.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
import { shallow } from 'zustand/shallow';
import { useEditToolsActions, useEditToolsStore } from '../store';
import { setQueryStringParam } from '../utils/setQueryStringParam';

const queryStringKey = 'devView';

const setQueryStringParam = (value: boolean) => {
const url = new URL(window.location.href);
if (value) {
url.searchParams.set(queryStringKey, value.toString());
} else {
url.searchParams.delete(queryStringKey);
}
window.history.pushState(null, '', url.toString());
};

export const useDevView = () => {
const [isDevView] = useEditToolsStore((store) => [store.isDevView], shallow);
const { setIsDevView } = useEditToolsActions();

const toggleDevView = () => {
const newDevViewValue = !isDevView;
setQueryStringParam(newDevViewValue);
setQueryStringParam(queryStringKey, newDevViewValue);
setIsDevView(newDevViewValue);
};

Expand Down
9 changes: 9 additions & 0 deletions packages/widget-playground/src/utils/setQueryStringParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const setQueryStringParam = (queryStringKey: string, value: boolean) => {
const url = new URL(window.location.href);
if (value) {
url.searchParams.set(queryStringKey, value.toString());
} else {
url.searchParams.delete(queryStringKey);
}
window.history.pushState(null, '', url.toString());
};
2 changes: 1 addition & 1 deletion packages/widget/src/stores/form/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export interface FormProps {
touchedFields: { [key in FormFieldNames]?: boolean };
}

interface ResetOptions {
export interface ResetOptions {
defaultValue?: GenericFormValue;
}

Expand Down
75 changes: 73 additions & 2 deletions packages/widget/src/stores/form/useFieldActions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import { useCallback } from 'react';
import { shallow } from 'zustand/shallow';
import type { FormActions } from './types.js';
import { useWidgetEvents } from '../../hooks/useWidgetEvents.js';
import type { FormFieldChanged } from '../../types/events.js';
import { WidgetEvent } from '../../types/events.js';
import type {
DefaultValues,
FormActions,
FormFieldNames,
GenericFormValue,
SetOptions,
} from './types.js';
import { useFormStore } from './useFormStore.js';

export const useFieldActions = () => {
const emitter = useWidgetEvents();
const actions = useFormStore<FormActions>(
(store) => ({
getFieldValues: store.getFieldValues,
Expand All @@ -16,5 +27,65 @@ export const useFieldActions = () => {
shallow,
);

return actions;
const setFieldValueWithEmittedEvents = useCallback(
(
fieldName: FormFieldNames,
newValue: GenericFormValue,
options?: SetOptions,
) => {
const oldValue = actions.getFieldValues(fieldName)[0];

actions.setFieldValue(fieldName, newValue, options);

if (newValue !== oldValue) {
emitter.emit(WidgetEvent.FormFieldChanged, {
fieldName,
newValue,
oldValue,
} as FormFieldChanged);
}
},
[actions, emitter],
);

const setUserAndDefaultValuesWithEmittedEvents = useCallback(
(formValues: Partial<DefaultValues>) => {
const formValuesKeys = Object.keys(formValues) as FormFieldNames[];

const changedValues = formValuesKeys.reduce(
(accum, fieldName) => {
const oldValue = actions.getFieldValues(fieldName)[0];
const newValue = formValues[fieldName];

if (newValue !== oldValue) {
accum.push({ fieldName, newValue, oldValue });
}

return accum;
},
[] as {
fieldName: FormFieldNames;
newValue: GenericFormValue;
oldValue: GenericFormValue;
}[],
);

actions.setUserAndDefaultValues(formValues);

changedValues.forEach(({ fieldName, newValue, oldValue }) => {
emitter.emit(WidgetEvent.FormFieldChanged, {
fieldName,
newValue,
oldValue,
} as FormFieldChanged);
});
},
[actions, emitter],
);

return {
...actions,
setFieldValue: setFieldValueWithEmittedEvents,
setUserAndDefaultValues: setUserAndDefaultValuesWithEmittedEvents,
};
};
11 changes: 11 additions & 0 deletions packages/widget/src/types/events.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ChainId, ChainType, Process, Route } from '@lifi/sdk';
import type { DefaultValues } from '@lifi/widget/stores/form/types.js';
import type { SettingsProps } from '../stores/settings/types.js';
import type { NavigationRouteType } from '../utils/navigationRoutes.js';

Expand All @@ -20,6 +21,7 @@ export enum WidgetEvent {
WalletConnected = 'walletConnected',
WidgetExpanded = 'widgetExpanded',
PageEntered = 'pageEntered',
FormFieldChanged = 'formFieldChanged',
SettingUpdated = 'settingUpdated',
}

Expand All @@ -34,6 +36,7 @@ export type WidgetEvents = {
sourceChainTokenSelected: ChainTokenSelected;
destinationChainTokenSelected: ChainTokenSelected;
sendToWalletToggled: boolean;
formFieldChanged: FormFieldChanged;
reviewTransactionPageEntered?: Route;
walletConnected: WalletConnected;
widgetExpanded: boolean;
Expand Down Expand Up @@ -69,6 +72,14 @@ export interface WalletConnected {
chainType?: ChainType;
}

export type FormFieldChanged = {
[K in keyof DefaultValues]: {
fieldName: K;
newValue: DefaultValues[K];
oldValue: DefaultValues[K];
};
}[keyof DefaultValues];

export type SettingUpdated<
K extends keyof SettingsProps = keyof SettingsProps,
> = {
Expand Down

0 comments on commit b247afb

Please sign in to comment.