keycloak/themes/zariot/keycloak-preview/account/resources/app/content/linked-accounts-page/LinkedAccountsPage.tsx
2025-02-18 15:55:19 +03:00

228 lines
9.6 KiB
TypeScript

/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import {withRouter, RouteComponentProps} from 'react-router-dom';
import {AxiosResponse} from 'axios';
import {
Badge,
Button,
DataList,
DataListAction,
DataListItemCells,
DataListCell,
DataListItemRow,
Stack,
StackItem,
Title,
TitleLevel,
DataListItem,
} from '@patternfly/react-core';
import {
BitbucketIcon,
CubeIcon,
FacebookIcon,
GithubIcon,
GitlabIcon,
GoogleIcon,
InstagramIcon,
LinkIcon,
LinkedinIcon,
MicrosoftIcon,
OpenshiftIcon,
PaypalIcon,
StackOverflowIcon,
TwitterIcon,
UnlinkIcon
} from '@patternfly/react-icons';
import {AccountServiceClient} from '../../account-service/account.service';
import {Msg} from '../../widgets/Msg';
import {ContentPage} from '../ContentPage';
import {createRedirect} from '../../util/RedirectUri';
interface LinkedAccount {
connected: boolean;
social: boolean;
providerAlias: string;
providerName: string;
displayName: string;
linkedUsername: string;
}
interface LinkedAccountsPageProps extends RouteComponentProps {
}
interface LinkedAccountsPageState {
linkedAccounts: LinkedAccount[];
unLinkedAccounts: LinkedAccount[];
}
/**
* @author Stan Silvert
*/
class LinkedAccountsPage extends React.Component<LinkedAccountsPageProps, LinkedAccountsPageState> {
public constructor(props: LinkedAccountsPageProps) {
super(props);
this.state = {
linkedAccounts: [],
unLinkedAccounts: []
}
this.getLinkedAccounts();
}
private getLinkedAccounts(): void {
AccountServiceClient.Instance.doGet("/linked-accounts")
.then((response: AxiosResponse<LinkedAccount[]>) => {
console.log({response});
const linkedAccounts = response.data.filter((account) => account.connected);
const unLinkedAccounts = response.data.filter((account) => !account.connected);
this.setState({linkedAccounts: linkedAccounts, unLinkedAccounts: unLinkedAccounts});
});
}
private unLinkAccount(account: LinkedAccount): void {
const url = '/linked-accounts/' + account.providerName;
AccountServiceClient.Instance.doDelete(url)
.then((response: AxiosResponse) => {
console.log({response});
this.getLinkedAccounts();
});
}
private linkAccount(account: LinkedAccount): void {
const url = '/linked-accounts/' + account.providerName;
const redirectUri: string = createRedirect(this.props.location.pathname);
AccountServiceClient.Instance.doGet(url, { params: {providerId: account.providerName, redirectUri}})
.then((response: AxiosResponse<{accountLinkUri: string}>) => {
console.log({response});
window.location.href = response.data.accountLinkUri;
});
}
public render(): React.ReactNode {
return (
<ContentPage title={Msg.localize('linkedAccountsTitle')} introMessage={Msg.localize('linkedAccountsIntroMessage')}>
<Stack gutter='md'>
<StackItem isFilled>
<Title headingLevel={TitleLevel.h2} size='2xl'>
<Msg msgKey='linkedLoginProviders'/>
</Title>
<DataList id="linked-idps" aria-label='foo'>
{this.makeRows(this.state.linkedAccounts, true)}
</DataList>
</StackItem>
<StackItem isFilled/>
<StackItem isFilled>
<Title headingLevel={TitleLevel.h2} size='2xl'>
<Msg msgKey='unlinkedLoginProviders'/>
</Title>
<DataList id="unlinked-idps" aria-label='foo'>
{this.makeRows(this.state.unLinkedAccounts, false)}
</DataList>
</StackItem>
</Stack>
</ContentPage>
);
}
private emptyRow(isLinked: boolean): React.ReactNode {
let isEmptyMessage = '';
if (isLinked) {
isEmptyMessage = Msg.localize('linkedEmpty');
} else {
isEmptyMessage = Msg.localize('unlinkedEmpty');
}
return (
<DataListItem key='emptyItem' aria-labelledby="empty-item">
<DataListItemRow key='emptyRow'>
<DataListItemCells dataListCells={[
<DataListCell key='empty'><strong>{isEmptyMessage}</strong></DataListCell>
]}/>
</DataListItemRow>
</DataListItem>
)
}
private makeRows(accounts: LinkedAccount[], isLinked: boolean): React.ReactNode {
if (accounts.length === 0) {
return this.emptyRow(isLinked);
}
return (
<> {
accounts.map( (account: LinkedAccount) => (
<DataListItem id={`${account.providerAlias}-idp`} key={account.providerName} aria-labelledby="simple-item1">
<DataListItemRow key={account.providerName}>
<DataListItemCells
dataListCells={[
<DataListCell key='idp'><Stack><StackItem isFilled>{this.findIcon(account)}</StackItem><StackItem id={`${account.providerAlias}-idp-name`} isFilled><h2><strong>{account.displayName}</strong></h2></StackItem></Stack></DataListCell>,
<DataListCell key='badge'><Stack><StackItem isFilled/><StackItem id={`${account.providerAlias}-idp-badge`} isFilled>{this.badge(account)}</StackItem></Stack></DataListCell>,
<DataListCell key='username'><Stack><StackItem isFilled/><StackItem id={`${account.providerAlias}-idp-username`} isFilled>{account.linkedUsername}</StackItem></Stack></DataListCell>,
]}/>
<DataListAction aria-labelledby='foo' aria-label='foo action' id='setPasswordAction'>
{isLinked && <Button id={`${account.providerAlias}-idp-unlink`} variant='link' onClick={() => this.unLinkAccount(account)}><UnlinkIcon size='sm'/> <Msg msgKey='unLink'/></Button>}
{!isLinked && <Button id={`${account.providerAlias}-idp-link`} variant='link' onClick={() => this.linkAccount(account)}><LinkIcon size='sm'/> <Msg msgKey='link'/></Button>}
</DataListAction>
</DataListItemRow>
</DataListItem>
))
} </>
)
}
private badge(account: LinkedAccount): React.ReactNode {
if (account.social) {
return (<Badge><Msg msgKey='socialLogin'/></Badge>);
}
return (<Badge style={{backgroundColor: "green"}} ><Msg msgKey='systemDefined'/></Badge>);
}
private findIcon(account: LinkedAccount): React.ReactNode {
const socialIconId = `${account.providerAlias}-idp-icon-social`;
if (account.providerName.toLowerCase().includes('github')) return (<GithubIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('linkedin')) return (<LinkedinIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('facebook')) return (<FacebookIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('google')) return (<GoogleIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('instagram')) return (<InstagramIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('microsoft')) return (<MicrosoftIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('bitbucket')) return (<BitbucketIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('twitter')) return (<TwitterIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('openshift')) return (<OpenshiftIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('gitlab')) return (<GitlabIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('paypal')) return (<PaypalIcon id={socialIconId} size='xl'/>);
if (account.providerName.toLowerCase().includes('stackoverflow')) return (<StackOverflowIcon id={socialIconId} size='xl'/>);
return (<CubeIcon id={`${account.providerAlias}-idp-icon-default`} size='xl'/>);
}
};
const LinkedAccountsPagewithRouter = withRouter(LinkedAccountsPage);
export {LinkedAccountsPagewithRouter as LinkedAccountsPage};