mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
feat(elements): add logto-account-center element (#6737)
This commit is contained in:
parent
f48d3a1f95
commit
50031369a1
5 changed files with 157 additions and 1 deletions
|
@ -10,7 +10,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color: #ecebf5;">
|
<body style="background-color: #ecebf5;">
|
||||||
<logto-account-provider>
|
<logto-account-provider>
|
||||||
<logto-username></logto-username>
|
<logto-account-center></logto-account-center>
|
||||||
</logto-account-provider>
|
</logto-account-provider>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
import { assert, fixture, html, waitUntil } from '@open-wc/testing';
|
||||||
|
|
||||||
|
import { createMockAccountApi } from '../__mocks__/account-api.js';
|
||||||
|
import { type LogtoAccountProvider } from '../providers/logto-account-provider.js';
|
||||||
|
|
||||||
|
import { LogtoAccountCenter } from './logto-account-center.js';
|
||||||
|
|
||||||
|
suite('logto-account-center', () => {
|
||||||
|
test('is defined', () => {
|
||||||
|
const element = document.createElement(LogtoAccountCenter.tagName);
|
||||||
|
assert.instanceOf(element, LogtoAccountCenter);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render error message when account context is not available', async () => {
|
||||||
|
const element = await fixture<LogtoAccountCenter>(
|
||||||
|
html`<logto-account-center></logto-account-center>`
|
||||||
|
);
|
||||||
|
await element.updateComplete;
|
||||||
|
|
||||||
|
assert.equal(element.shadowRoot?.textContent?.trim(), 'Unable to retrieve account context.');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should render components correctly based on user info', async () => {
|
||||||
|
const mockAccountApi = createMockAccountApi({
|
||||||
|
fetchUserProfile: async () => ({
|
||||||
|
username: 'testuser',
|
||||||
|
primaryEmail: 'test@example.com',
|
||||||
|
primaryPhone: '1234567890',
|
||||||
|
hasPassword: true,
|
||||||
|
identities: {
|
||||||
|
google: {
|
||||||
|
userId: 'google-123',
|
||||||
|
details: { name: 'John Doe', email: 'john@example.com' },
|
||||||
|
},
|
||||||
|
facebook: {
|
||||||
|
userId: 'facebook-123',
|
||||||
|
details: { name: 'Jane Doe', email: 'jane@example.com' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const provider = await fixture<LogtoAccountProvider>(
|
||||||
|
html`<logto-account-provider .accountApi=${mockAccountApi}>
|
||||||
|
<logto-account-center></logto-account-center>
|
||||||
|
</logto-account-provider>`
|
||||||
|
);
|
||||||
|
|
||||||
|
await provider.updateComplete;
|
||||||
|
|
||||||
|
const accountCenter = provider.querySelector<LogtoAccountCenter>(LogtoAccountCenter.tagName);
|
||||||
|
await accountCenter?.updateComplete;
|
||||||
|
|
||||||
|
await waitUntil(() => {
|
||||||
|
const shadowRoot = accountCenter?.shadowRoot;
|
||||||
|
return (
|
||||||
|
shadowRoot?.querySelector('logto-username') &&
|
||||||
|
shadowRoot.querySelector('logto-user-email') &&
|
||||||
|
shadowRoot.querySelector('logto-user-phone') &&
|
||||||
|
shadowRoot.querySelector('logto-user-password') &&
|
||||||
|
shadowRoot.querySelectorAll('logto-social-identity').length === 2
|
||||||
|
);
|
||||||
|
}, 'Unable to render all expected components');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should only render components for existing user info', async () => {
|
||||||
|
const mockAccountApi = createMockAccountApi({
|
||||||
|
fetchUserProfile: async () => ({
|
||||||
|
username: 'testuser',
|
||||||
|
primaryEmail: undefined,
|
||||||
|
primaryPhone: undefined,
|
||||||
|
hasPassword: undefined,
|
||||||
|
identities: {},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const provider = await fixture<LogtoAccountProvider>(
|
||||||
|
html`<logto-account-provider .accountApi=${mockAccountApi}>
|
||||||
|
<logto-account-center></logto-account-center>
|
||||||
|
</logto-account-provider>`
|
||||||
|
);
|
||||||
|
|
||||||
|
await provider.updateComplete;
|
||||||
|
|
||||||
|
const accountCenter = provider.querySelector<LogtoAccountCenter>(LogtoAccountCenter.tagName);
|
||||||
|
await accountCenter?.updateComplete;
|
||||||
|
|
||||||
|
const shadowRoot = accountCenter?.shadowRoot;
|
||||||
|
assert.exists(shadowRoot?.querySelector('logto-username'));
|
||||||
|
assert.notExists(shadowRoot.querySelector('logto-user-email'));
|
||||||
|
assert.notExists(shadowRoot.querySelector('logto-user-phone'));
|
||||||
|
assert.notExists(shadowRoot.querySelector('logto-user-password'));
|
||||||
|
assert.notExists(shadowRoot.querySelector('logto-social-identity'));
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { consume } from '@lit/context';
|
||||||
|
import { css, html, LitElement } from 'lit';
|
||||||
|
import { customElement } from 'lit/decorators.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
logtoAccountContext,
|
||||||
|
type LogtoAccountContextType,
|
||||||
|
} from '../providers/logto-account-provider.js';
|
||||||
|
|
||||||
|
const tagName = 'logto-account-center';
|
||||||
|
|
||||||
|
@customElement(tagName)
|
||||||
|
export class LogtoAccountCenter extends LitElement {
|
||||||
|
static tagName = tagName;
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--logto-account-center-item-spacing, var(--logto-spacing-md));
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
@consume({ context: logtoAccountContext, subscribe: true })
|
||||||
|
private readonly accountContext?: LogtoAccountContextType;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.accountContext) {
|
||||||
|
return html`<span>Unable to retrieve account context.</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
userProfile: { username, primaryEmail, primaryPhone, hasPassword, identities },
|
||||||
|
} = this.accountContext;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${username !== undefined && html`<logto-username></logto-username>`}
|
||||||
|
${primaryEmail !== undefined && html`<logto-user-email></logto-user-email>`}
|
||||||
|
${primaryPhone !== undefined && html`<logto-user-phone></logto-user-phone>`}
|
||||||
|
${hasPassword !== undefined && html`<logto-user-password></logto-user-password>`}
|
||||||
|
${identities !== undefined &&
|
||||||
|
Object.entries(identities).map(
|
||||||
|
([target]) => html`<logto-social-identity target=${target}></logto-social-identity>`
|
||||||
|
)}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
[tagName]: LogtoAccountCenter;
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,3 +11,4 @@ export * from './elements/logto-user-email.js';
|
||||||
export * from './elements/logto-user-password.js';
|
export * from './elements/logto-user-password.js';
|
||||||
export * from './elements/logto-user-phone.js';
|
export * from './elements/logto-user-phone.js';
|
||||||
export * from './elements/logto-social-identity.js';
|
export * from './elements/logto-social-identity.js';
|
||||||
|
export * from './elements/logto-account-center.js';
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { createComponent } from '@lit/react';
|
||||||
|
|
||||||
import { LogtoUsername } from './elements/logto-username.js';
|
import { LogtoUsername } from './elements/logto-username.js';
|
||||||
import {
|
import {
|
||||||
|
LogtoAccountCenter,
|
||||||
LogtoAccountProvider,
|
LogtoAccountProvider,
|
||||||
LogtoSocialIdentity,
|
LogtoSocialIdentity,
|
||||||
LogtoUserEmail,
|
LogtoUserEmail,
|
||||||
|
@ -43,5 +44,10 @@ export const createReactComponents = (react: Parameters<typeof createComponent>[
|
||||||
elementClass: LogtoSocialIdentity,
|
elementClass: LogtoSocialIdentity,
|
||||||
react,
|
react,
|
||||||
}),
|
}),
|
||||||
|
LogtoAccountCenter: createComponent({
|
||||||
|
tagName: LogtoAccountCenter.tagName,
|
||||||
|
elementClass: LogtoAccountCenter,
|
||||||
|
react,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue