Skip to content

Commit

Permalink
feat(elements): add logto-social-identity element (#6736)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaoyijun authored Oct 25, 2024
1 parent 3d25448 commit 8f59a20
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 0 deletions.
88 changes: 88 additions & 0 deletions packages/elements/src/account/components/logto-identity-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { css, html, LitElement } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';

import fallbackAvatar from '../icons/fallback-avatar.svg';

const tagName = 'logto-identity-info';

@customElement(tagName)
export class LogtoIdentityInfo extends LitElement {
static tagName = tagName;

static styles = css`
:host {
display: flex;
align-items: center;
gap: var(--logto-spacing-sm);
}
.avatar {
--logto-icon-size: var(--logto-identity-info-avatar-size, 36px);
> img {
display: block;
width: var(--logto-identity-info-avatar-size, 36px);
height: var(--logto-identity-info-avatar-size, 36px);
border-radius: var(--logto-identity-info-avatar-shape, var(--logto-shape-corner-md));
}
}
.info {
flex: 1;
flex-direction: column;
.name {
font: var(--logto-identity-info-name-font-size, var(--logto-font-body-md));
color: var(
--logto-identity-info-name-color,
var(--logto-color---logto-color-typeface-primary)
);
}
.email {
font: var(--logto-identity-info-email-font, var(--logto-font-body-sm));
color: var(
--logto-identity-info-email-color,
var(--logto-color---logto-color-typeface-primary)
);
}
}
`;

@property({ type: String })
avatar = '';

@property({ type: String })
name = '';

@property({ type: String })
email = '';

@state()
failedToLoadAvatar = false;

render() {
return html`
<div class="avatar">
${this.avatar && !this.failedToLoadAvatar
? html`<img src="${this.avatar}" alt="user avatar" @error=${this.handleAvatarError} />`
: html`<logto-icon>${fallbackAvatar}</logto-icon>`}
</div>
<div class="info">
<div class="name">${this.name}</div>
<div class="email">${this.email}</div>
</div>
`;
}

private handleAvatarError() {
this.failedToLoadAvatar = true;
}
}

declare global {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface HTMLElementTagNameMap {
[tagName]: LogtoIdentityInfo;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { assert, fixture, html, waitUntil } from '@open-wc/testing';

import { createMockAccountApi } from '../__mocks__/account-api.js';
import { LogtoIdentityInfo } from '../components/logto-identity-info.js';
import { type LogtoAccountProvider } from '../providers/logto-account-provider.js';

import { LogtoSocialIdentity } from './logto-social-identity.js';

suite('logto-social-identity', () => {
test('is defined', () => {
const element = document.createElement(LogtoSocialIdentity.tagName);
assert.instanceOf(element, LogtoSocialIdentity);
});

test('should render error message when account context is not available', async () => {
const element = await fixture<LogtoSocialIdentity>(
html`<logto-social-identity></logto-social-identity>`
);
await element.updateComplete;

assert.equal(element.shadowRoot?.textContent, 'Unable to retrieve account context.');
});

test('should render correctly when user has permission to view social identity information', async () => {
const mockAccountApi = createMockAccountApi({
fetchUserProfile: async () => ({
identities: {
github: {
userId: '123',
details: {
name: 'John Doe',
email: '[email protected]',
},
},
},
}),
});

const provider = await fixture<LogtoAccountProvider>(
html`<logto-account-provider .accountApi=${mockAccountApi}>
<logto-social-identity target="github"></logto-social-identity>
</logto-account-provider>`
);

await provider.updateComplete;

const logtoSocialIdentity = provider.querySelector<LogtoSocialIdentity>(
LogtoSocialIdentity.tagName
);

const identityInfo = logtoSocialIdentity?.shadowRoot?.querySelector<LogtoIdentityInfo>(
LogtoIdentityInfo.tagName
);

await waitUntil(
() =>
identityInfo?.shadowRoot?.querySelector('div[class=name]')?.textContent === 'John Doe' &&
identityInfo.shadowRoot.querySelector('div[class=email]')?.textContent ===
'[email protected]',
'Unable to get social identity information from account context'
);
});

test('should render nothing if the user lacks permission to view social identity information', async () => {
const mockAccountApi = createMockAccountApi({
fetchUserProfile: async () => ({
identities: undefined,
}),
});

const provider = await fixture<LogtoAccountProvider>(
html`<logto-account-provider .accountApi=${mockAccountApi}>
<logto-social-identity target="github" labelText="GitHub"></logto-social-identity>
</logto-account-provider>`
);

await provider.updateComplete;
const logtoSocialIdentity = provider.querySelector<LogtoSocialIdentity>(
LogtoSocialIdentity.tagName
);

await logtoSocialIdentity?.updateComplete;
assert.equal(logtoSocialIdentity?.shadowRoot?.children.length, 0);
});
});
55 changes: 55 additions & 0 deletions packages/elements/src/account/elements/logto-social-identity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { html, type TemplateResult } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';

import usernameIcon from '../icons/username.svg';

import { LogtoProfileItemElement } from './LogtoProfileItemElement.js';

const tagName = 'logto-social-identity';

@customElement(tagName)
export class LogtoSocialIdentity extends LogtoProfileItemElement {
static tagName = tagName;

@property({ type: String })
target = '';

protected isAccessible(): boolean {
return this.accountContext?.userProfile.identities !== undefined;
}

protected getItemLabelInfo() {
// Todo: @xiaoyijun replace with correct label text and icon when related connector API is ready
return {
icon: usernameIcon,
label: this.target,
};
}

protected renderContent(): TemplateResult {
const { identities } = this.accountContext?.userProfile ?? {};

const identity = identities?.[this.target];
// Todo: @xiaoyijun support identifier fallback logic
const { avatar = '', name = '', email = '' } = identity?.details ?? {};

return when(
identity,
() =>
html`<logto-identity-info
slot="content"
.avatar=${String(avatar)}
.name=${String(name)}
.email=${String(email)}
></logto-identity-info>`
);
}
}

declare global {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
interface HTMLElementTagNameMap {
[tagName]: LogtoSocialIdentity;
}
}
13 changes: 13 additions & 0 deletions packages/elements/src/account/icons/fallback-avatar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions packages/elements/src/account/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ export * from './api/index.js';

export * from './components/logto-icon.js';
export * from './components/logto-profile-item.js';
export * from './components/logto-identity-info.js';

export * from './providers/logto-account-provider.js';

export * from './elements/logto-username.js';
export * from './elements/logto-user-email.js';
export * from './elements/logto-user-password.js';
export * from './elements/logto-user-phone.js';
export * from './elements/logto-social-identity.js';
6 changes: 6 additions & 0 deletions packages/elements/src/account/react.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createComponent } from '@lit/react';
import { LogtoUsername } from './elements/logto-username.js';
import {
LogtoAccountProvider,
LogtoSocialIdentity,
LogtoUserEmail,
LogtoUserPassword,
LogtoUserPhone,
Expand Down Expand Up @@ -37,5 +38,10 @@ export const createReactComponents = (react: Parameters<typeof createComponent>[
elementClass: LogtoUserPhone,
react,
}),
LogtoSocialIdentity: createComponent({
tagName: LogtoSocialIdentity.tagName,
elementClass: LogtoSocialIdentity,
react,
}),
};
};

0 comments on commit 8f59a20

Please sign in to comment.