diff --git a/apps/sample-angular-app/src/app/app.component.html b/apps/sample-angular-app/src/app/app.component.html index 0743ce26..ff9aa61a 100644 --- a/apps/sample-angular-app/src/app/app.component.html +++ b/apps/sample-angular-app/src/app/app.component.html @@ -1 +1 @@ - + diff --git a/apps/sample-vanilla-app/index.html b/apps/sample-vanilla-app/index.html index 14c9b7c3..94652c81 100644 --- a/apps/sample-vanilla-app/index.html +++ b/apps/sample-vanilla-app/index.html @@ -8,8 +8,8 @@ - + > diff --git a/apps/sample-vue-app/src/App.vue b/apps/sample-vue-app/src/App.vue index 667e9fc4..202753be 100644 --- a/apps/sample-vue-app/src/App.vue +++ b/apps/sample-vue-app/src/App.vue @@ -2,9 +2,9 @@ Vue logo
- + >
diff --git a/packages/dapp-kit-ui/package.json b/packages/dapp-kit-ui/package.json index cfe9e5f2..80da36d3 100644 --- a/packages/dapp-kit-ui/package.json +++ b/packages/dapp-kit-ui/package.json @@ -25,6 +25,7 @@ "watch": "tsup --watch" }, "dependencies": { + "@lit/context": "^1.1.0", "@vechain/picasso": "2.1.1", "@vechainfoundation/dapp-kit": "*", "@wagmi/core": "^1.4.5", diff --git a/packages/dapp-kit-ui/src/components/index.ts b/packages/dapp-kit-ui/src/components/index.ts index e2a8bbd5..29a6df33 100644 --- a/packages/dapp-kit-ui/src/components/index.ts +++ b/packages/dapp-kit-ui/src/components/index.ts @@ -1,4 +1,5 @@ import './base'; +import './provider'; import './vwk-connect-modal'; import './vwk-connect-button'; import './vwk-connect-button-with-modal'; @@ -8,8 +9,10 @@ import './vwk-connected-address-badge-with-modal'; import './vwk-source-card'; import './vwk-fonts'; import './vwk-wallet-connect-qrcode'; +import './vwk-vechain-dapp-connect-kit'; export * from './base'; +export * from './provider'; export * from './vwk-connect-modal'; export * from './vwk-connect-button'; export * from './vwk-connect-button-with-modal'; @@ -19,3 +22,4 @@ export * from './vwk-connected-address-badge-with-modal'; export * from './vwk-source-card'; export * from './vwk-fonts'; export * from './vwk-wallet-connect-qrcode'; +export * from './vwk-vechain-dapp-connect-kit'; diff --git a/packages/dapp-kit-ui/src/components/provider/dapp-kit-context-provider/index.ts b/packages/dapp-kit-ui/src/components/provider/dapp-kit-context-provider/index.ts new file mode 100644 index 00000000..c41b4214 --- /dev/null +++ b/packages/dapp-kit-ui/src/components/provider/dapp-kit-context-provider/index.ts @@ -0,0 +1,39 @@ +import { provide } from '@lit/context'; +import { LitElement, type TemplateResult, html } from 'lit'; +import { customElement, property } from 'lit/decorators.js'; +import { + type DappKitContext, + dappKitContext, + getDappKitContext, +} from '../dapp-kit-context'; + +@customElement('dapp-kit-context-provider') +export class DappKitContextProvider extends LitElement { + @provide({ context: dappKitContext }) + @property({ attribute: false }) + dappKitContext: DappKitContext = { + options: { + notPersistentContext: false, + }, + address: '', + }; + + @property({ type: Boolean }) + notPersistentContext = false; + + // Use the `connectedCallback` lifecycle hook to retrieve the stored address + connectedCallback(): void { + super.connectedCallback(); + this.dappKitContext = getDappKitContext(this.notPersistentContext); + } + + render(): TemplateResult { + return html``; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'dapp-kit-context-provider': DappKitContextProvider; + } +} diff --git a/packages/dapp-kit-ui/src/components/provider/dapp-kit-context.ts b/packages/dapp-kit-ui/src/components/provider/dapp-kit-context.ts new file mode 100644 index 00000000..4ec2c10c --- /dev/null +++ b/packages/dapp-kit-ui/src/components/provider/dapp-kit-context.ts @@ -0,0 +1,44 @@ +import { createContext } from '@lit/context'; + +interface DappKitContextOptions { + notPersistentContext: boolean; +} + +interface DappKitContext { + options: DappKitContextOptions; + address: string; +} + +const dappKitContext = createContext( + Symbol('dapp-kit-context'), +); + +const storeDappKitContext = function (context: DappKitContext): void { + localStorage.setItem('dapp-kit-context-object', JSON.stringify(context)); +}; + +const getDappKitContext = function ( + notPersistentContext = false, +): DappKitContext { + const dappKitContextObject = localStorage.getItem( + 'dapp-kit-context-object', + ); + + if (notPersistentContext || !dappKitContextObject) { + return { + options: { + notPersistentContext, + }, + address: '', + }; + } + + return JSON.parse(dappKitContextObject) as DappKitContext; +}; + +export { + dappKitContext, + type DappKitContext, + storeDappKitContext, + getDappKitContext, +}; diff --git a/packages/dapp-kit-ui/src/components/provider/index.ts b/packages/dapp-kit-ui/src/components/provider/index.ts new file mode 100644 index 00000000..a43172d3 --- /dev/null +++ b/packages/dapp-kit-ui/src/components/provider/index.ts @@ -0,0 +1,2 @@ +export * from './dapp-kit-context'; +export * from './dapp-kit-context-provider'; diff --git a/packages/dapp-kit-ui/src/components/vwk-connect-button-with-modal/index.ts b/packages/dapp-kit-ui/src/components/vwk-connect-button-with-modal/index.ts index 503b0d57..67363d41 100644 --- a/packages/dapp-kit-ui/src/components/vwk-connect-button-with-modal/index.ts +++ b/packages/dapp-kit-ui/src/components/vwk-connect-button-with-modal/index.ts @@ -1,12 +1,22 @@ -import type { TemplateResult } from 'lit'; -import { html, LitElement } from 'lit'; +import { consume } from '@lit/context'; +import { LitElement, type TemplateResult, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import type { WalletManager } from '@vechainfoundation/dapp-kit'; -import type { SourceInfo, Theme, ThemeMode } from '../../constants'; +import { dappKitContext, storeDappKitContext } from '../provider'; import { DAppKit } from '../../client'; +import type { SourceInfo, Theme, ThemeMode } from '../../constants'; @customElement('vwk-connect-button-with-modal') export class ConnectButtonWithModal extends LitElement { + @consume({ context: dappKitContext }) + @property({ attribute: false }) + dappKitContext = { + options: { + notPersistentContext: false, + }, + address: '', + }; + @property() override title = 'Connect Wallet'; @@ -19,9 +29,6 @@ export class ConnectButtonWithModal extends LitElement { @property({ type: Boolean }) open = false; - @property({ type: String }) - address?: string; - private get wallet(): WalletManager { return DAppKit.connex.wallet; } @@ -32,8 +39,9 @@ export class ConnectButtonWithModal extends LitElement { this.wallet.setSource(source.id); this.wallet .connect() - // eslint-disable-next-line no-console - .then((res) => (this.address = res.account)) + .then((res) => { + this.updateAddress(res.account); + }) .finally(() => { this.open = false; }); @@ -43,7 +51,7 @@ export class ConnectButtonWithModal extends LitElement { @property({ type: Function }) onDisconnectClick = (): void => { this.wallet.disconnect().finally(() => { - this.address = undefined; + this.updateAddress(''); }); }; @@ -51,12 +59,11 @@ export class ConnectButtonWithModal extends LitElement { return html`
- - ${this.address + ${this.dappKitContext.address ? html`` : html` { + this.dappKitContext.address = address; + + // store the context object in local storage if the context is persistent + if (!this.dappKitContext.options.notPersistentContext) { + storeDappKitContext(this.dappKitContext); + } + // render the component again after the address is updated + this.requestUpdate(); + }; + private handleOpen = (): void => { DAppKit.connex.wallet.disconnect().finally(() => { this.open = true; diff --git a/packages/dapp-kit-ui/src/components/vwk-connect-modal/index.ts b/packages/dapp-kit-ui/src/components/vwk-connect-modal/index.ts index ee80f9cb..3ee10877 100644 --- a/packages/dapp-kit-ui/src/components/vwk-connect-modal/index.ts +++ b/packages/dapp-kit-ui/src/components/vwk-connect-modal/index.ts @@ -80,14 +80,19 @@ export class ConnectModal extends LitElement { color: ${Colors.LightGrey}; } `; + @property({ type: Boolean }) open = false; + @property({ type: Function }) onSourceClick?: (source?: SourceInfo) => void = undefined; + @property() mode: ThemeMode = 'LIGHT'; + @property() theme: Theme = 'DEFAULT'; + @property() walletConnectQRcode?: string = undefined; diff --git a/packages/dapp-kit-ui/src/components/vwk-connected-address-badge/index.ts b/packages/dapp-kit-ui/src/components/vwk-connected-address-badge/index.ts index b5a7e9d0..b4f1db27 100644 --- a/packages/dapp-kit-ui/src/components/vwk-connected-address-badge/index.ts +++ b/packages/dapp-kit-ui/src/components/vwk-connected-address-badge/index.ts @@ -1,6 +1,6 @@ -import { LitElement, css, html } from 'lit'; +import { LitElement, type TemplateResult, css, html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; -import { Colors, ThemeMode } from '../../constants'; +import { Colors, type ThemeMode } from '../../constants'; import { friendlyAddress, getPicassoImage } from '../../utils/account'; @customElement('vwk-connected-address-badge') @@ -57,7 +57,7 @@ export class ConnectedAddressBadge extends LitElement { @property({ type: Function }) onClick? = undefined; - render() { + render(): TemplateResult { return html`
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'vwk-vechain-dapp-connect-kit': VechainDappConnectKit; + } +} diff --git a/packages/dapp-kit-ui/test/connect-button-with-modal.test.ts b/packages/dapp-kit-ui/test/connect-button-with-modal.test.ts index b683f39d..2d9692ce 100644 --- a/packages/dapp-kit-ui/test/connect-button-with-modal.test.ts +++ b/packages/dapp-kit-ui/test/connect-button-with-modal.test.ts @@ -62,7 +62,8 @@ describe('connect-button-with-modal', () => { // testing the connected address badge // mock a connection to the wallet by setting the address - element.address = '0x00000'; + element.dappKitContext.address = '0x00000'; + element.requestUpdate(); const connectedAddressBadgeWithModal = (await elementQueries.getConnectedAddressBadgeWithModal()) as ConnectedAddressBadgeWithModal; @@ -87,6 +88,6 @@ describe('connect-button-with-modal', () => { await element.updateComplete; - expect(element.address).toBeUndefined(); + expect(element.dappKitContext.address).toBe(''); }); }); diff --git a/packages/dapp-kit-ui/test/helpers/element-queries.ts b/packages/dapp-kit-ui/test/helpers/element-queries.ts index 1981d28b..5e6eef94 100644 --- a/packages/dapp-kit-ui/test/helpers/element-queries.ts +++ b/packages/dapp-kit-ui/test/helpers/element-queries.ts @@ -4,6 +4,7 @@ import { ConnectedAddressBadge, ConnectedAddressBadgeWithModal, ConnectedAddressModal, + DappKitContextProvider, SourceCard, } from '../../src'; @@ -53,6 +54,16 @@ const getConnectedAddressBadgeWithModal = ( ); }; +const getDappKitContextProvider = (): Promise< + DappKitContextProvider | undefined | null +> => { + return performQueryWithTimeout(2000, () => + window.document.body + .querySelector('vwk-vechain-dapp-connect-kit') + ?.shadowRoot?.querySelector('dapp-kit-context-provider'), + ); +}; + const getConnectedAddressBadge = ( timeout = 2000, ): Promise => { @@ -115,4 +126,5 @@ export const elementQueries = { getConnectedAddressBadgeWithModal, getConnectedAddressBadge, getConnectedAddressModal, + getDappKitContextProvider, }; diff --git a/packages/dapp-kit-ui/test/vechain-dapp-connect-kit.test.ts b/packages/dapp-kit-ui/test/vechain-dapp-connect-kit.test.ts new file mode 100644 index 00000000..dcd5898f --- /dev/null +++ b/packages/dapp-kit-ui/test/vechain-dapp-connect-kit.test.ts @@ -0,0 +1,34 @@ +import { beforeEach, describe, expect, it } from 'vitest'; + +import { DAppKit, DappKitContextProvider, VechainDappConnectKit } from '../src'; +import { elementQueries } from './helpers/element-queries'; + +describe('connect-button-with-modal', () => { + beforeEach(() => { + DAppKit.configure({ nodeUrl: 'https://mainnet.vechain.org/' }); + }); + + it('Should callback with source when user clicks a wallet and should render the connected address badge once connected', async () => { + const element: VechainDappConnectKit = window.document.createElement( + 'vwk-vechain-dapp-connect-kit', + ); + + window.document.body.appendChild(element); + + // testing the dapp kit context provider + + // set a not persistent context + element.notPersistentContext = true; + + const dappKitContextProvider = + (await elementQueries.getDappKitContextProvider()) as DappKitContextProvider; + + expect(dappKitContextProvider).toBeDefined(); + + // check if the context is not persistent + expect(dappKitContextProvider.notPersistentContext).toBe(true); + + expect(dappKitContextProvider.dappKitContext).toBeDefined(); + expect(dappKitContextProvider.dappKitContext.address).toBe(''); + }); +}); diff --git a/yarn.lock b/yarn.lock index 961009fa..0a8175fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4435,11 +4435,18 @@ dependencies: "@lezer/common" "^1.0.0" -"@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0", "@lit-labs/ssr-dom-shim@^1.1.2-pre.0": +"@lit-labs/ssr-dom-shim@^1.0.0", "@lit-labs/ssr-dom-shim@^1.1.0", "@lit-labs/ssr-dom-shim@^1.1.2", "@lit-labs/ssr-dom-shim@^1.1.2-pre.0": version "1.1.2" resolved "https://registry.yarnpkg.com/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz#d693d972974a354034454ec1317eb6afd0b00312" integrity sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g== +"@lit/context@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@lit/context/-/context-1.1.0.tgz#c45be81b3fbcefe6844b86d9ca66aa7d88953c3d" + integrity sha512-fCyv4dsH05wCNm3AKbB+PdYbXGJd/XT8OOwo4hVmD4COq5wOWJlQreGAMDvmHZ7osqxuu06Y4nmP6ooXpN7ErA== + dependencies: + "@lit/reactive-element" "^1.6.2 || ^2.0.0" + "@lit/react@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@lit/react/-/react-1.0.1.tgz#00dc227c60947abd3cbdaa03bb9cca7f5b815451" @@ -4452,6 +4459,13 @@ dependencies: "@lit-labs/ssr-dom-shim" "^1.0.0" +"@lit/reactive-element@^1.6.2 || ^2.0.0": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-2.0.2.tgz#779ae9d265407daaf7737cb892df5ec2a86e22a0" + integrity sha512-SVOwLAWUQg3Ji1egtOt1UiFe4zdDpnWHyc5qctSceJ5XIu0Uc76YmGpIjZgx9YJ0XtdW0Jm507sDvjOu+HnB8w== + dependencies: + "@lit-labs/ssr-dom-shim" "^1.1.2" + "@lit/reactive-element@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-2.0.0.tgz#da14a256ac5533873b935840f306d572bac4a2ab"