mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-05-31 14:11:58 +05:30
Create app namespace for all absolute requires of app modules. Move all packages under packages yarn workspace
This commit is contained in:
303
packages/app/components/dev/apps/list/ApplicationItem.tsx
Normal file
303
packages/app/components/dev/apps/list/ApplicationItem.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import { SKIN_LIGHT, COLOR_BLACK, COLOR_RED } from 'app/components/ui';
|
||||
import { Input, Button } from 'app/components/ui/form';
|
||||
import { OauthAppResponse } from 'app/services/api/oauth';
|
||||
import Collapse from 'app/components/ui/collapse';
|
||||
|
||||
import styles from '../applicationsIndex.scss';
|
||||
import messages from '../ApplicationsIndex.intl.json';
|
||||
|
||||
const ACTION_REVOKE_TOKENS = 'revoke-tokens';
|
||||
const ACTION_RESET_SECRET = 'reset-secret';
|
||||
const ACTION_DELETE = 'delete';
|
||||
const actionButtons = [
|
||||
{
|
||||
type: ACTION_REVOKE_TOKENS,
|
||||
label: messages.revokeAllTokens,
|
||||
},
|
||||
{
|
||||
type: ACTION_RESET_SECRET,
|
||||
label: messages.resetClientSecret,
|
||||
},
|
||||
{
|
||||
type: ACTION_DELETE,
|
||||
label: messages.delete,
|
||||
},
|
||||
];
|
||||
|
||||
interface State {
|
||||
selectedAction: string | null;
|
||||
isActionPerforming: boolean;
|
||||
detailsHeight: number;
|
||||
translateY: number;
|
||||
}
|
||||
|
||||
export default class ApplicationItem extends React.Component<
|
||||
{
|
||||
application: OauthAppResponse;
|
||||
expand: boolean;
|
||||
onTileClick: (clientId: string) => void;
|
||||
onResetSubmit: (
|
||||
clientId: string,
|
||||
resetClientSecret: boolean,
|
||||
) => Promise<void>;
|
||||
onDeleteSubmit: (clientId: string) => Promise<void>;
|
||||
},
|
||||
State
|
||||
> {
|
||||
state: State = {
|
||||
selectedAction: null,
|
||||
isActionPerforming: false,
|
||||
translateY: 0,
|
||||
detailsHeight: 0,
|
||||
};
|
||||
|
||||
actionContainer: HTMLDivElement | null;
|
||||
|
||||
render() {
|
||||
const { application: app, expand } = this.props;
|
||||
const { selectedAction, translateY } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(styles.appItemContainer, {
|
||||
[styles.appExpanded]: expand,
|
||||
})}
|
||||
data-e2e="appItem"
|
||||
data-e2e-app-name={app.name}
|
||||
>
|
||||
<div className={styles.appItemTile} onClick={this.onTileToggle}>
|
||||
<div className={styles.appTileTitle}>
|
||||
<div className={styles.appName}>{app.name}</div>
|
||||
<div className={styles.appStats}>
|
||||
Client ID: {app.clientId}
|
||||
{typeof app.countUsers !== 'undefined' && (
|
||||
<span>
|
||||
{' | '}
|
||||
<Message
|
||||
{...messages.countUsers}
|
||||
values={{
|
||||
count: app.countUsers,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.appItemToggle}>
|
||||
<div className={styles.appItemToggleIcon} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Collapse isOpened={expand} onRest={this.onCollapseRest}>
|
||||
<div
|
||||
className={styles.appDetailsContainer}
|
||||
style={{ transform: `translateY(-${translateY}px)` }}
|
||||
>
|
||||
<div className={styles.appDetailsInfoField}>
|
||||
<Link
|
||||
to={`/dev/applications/${app.clientId}`}
|
||||
className={styles.editAppLink}
|
||||
>
|
||||
<Message
|
||||
{...messages.editDescription}
|
||||
values={{
|
||||
icon: <div className={styles.pencilIcon} />,
|
||||
}}
|
||||
/>
|
||||
</Link>
|
||||
<Input
|
||||
label="Client ID:"
|
||||
skin={SKIN_LIGHT}
|
||||
disabled
|
||||
value={app.clientId}
|
||||
copy
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.appDetailsInfoField}>
|
||||
<Input
|
||||
label="Client Secret:"
|
||||
skin={SKIN_LIGHT}
|
||||
disabled
|
||||
value={app.clientSecret}
|
||||
copy
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.appDetailsDescription}>
|
||||
<Message
|
||||
{...messages.ifYouSuspectingThatSecretHasBeenCompromised}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.appActionsButtons}>
|
||||
{actionButtons.map(({ type, label }) => (
|
||||
<Button
|
||||
key={type}
|
||||
label={label}
|
||||
color={COLOR_BLACK}
|
||||
className={styles.appActionButton}
|
||||
disabled={!!selectedAction && selectedAction !== type}
|
||||
onClick={this.onActionButtonClick(type)}
|
||||
small
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={styles.appActionContainer}
|
||||
ref={el => {
|
||||
this.actionContainer = el;
|
||||
}}
|
||||
>
|
||||
{this.getActionContent()}
|
||||
</div>
|
||||
</div>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getActionContent() {
|
||||
const { selectedAction, isActionPerforming } = this.state;
|
||||
|
||||
switch (selectedAction) {
|
||||
case ACTION_REVOKE_TOKENS:
|
||||
case ACTION_RESET_SECRET:
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.appActionDescription}>
|
||||
<Message {...messages.allRefreshTokensWillBecomeInvalid} />{' '}
|
||||
<Message {...messages.takeCareAccessTokensInvalidation} />
|
||||
</div>
|
||||
<div className={styles.appActionsButtons}>
|
||||
<Button
|
||||
label={messages.cancel}
|
||||
color={COLOR_BLACK}
|
||||
className={styles.appActionButton}
|
||||
onClick={this.onActionButtonClick(null)}
|
||||
small
|
||||
/>
|
||||
<div className={styles.continueActionButtonWrapper}>
|
||||
{isActionPerforming ? (
|
||||
<div className={styles.performingAction}>
|
||||
<Message {...messages.performing} />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={styles.continueActionLink}
|
||||
onClick={this.onResetSubmit(
|
||||
selectedAction === ACTION_RESET_SECRET,
|
||||
)}
|
||||
>
|
||||
<Message {...messages.continue} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
case ACTION_DELETE:
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.appActionDescription}>
|
||||
<Message {...messages.appAndAllTokenWillBeDeleted} />{' '}
|
||||
<Message {...messages.takeCareAccessTokensInvalidation} />
|
||||
</div>
|
||||
<div className={styles.appActionsButtons}>
|
||||
<Button
|
||||
label={messages.cancel}
|
||||
color={COLOR_BLACK}
|
||||
className={styles.appActionButton}
|
||||
onClick={this.onActionButtonClick(null)}
|
||||
small
|
||||
/>
|
||||
<div className={styles.continueActionButtonWrapper}>
|
||||
{isActionPerforming ? (
|
||||
<div className={styles.performingAction}>
|
||||
<Message {...messages.performing} />
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
label={messages.delete}
|
||||
color={COLOR_RED}
|
||||
className={styles.appActionButton}
|
||||
onClick={this.onSubmitDelete}
|
||||
small
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
setActiveAction = (type: string | null) => {
|
||||
const { actionContainer } = this;
|
||||
|
||||
if (!actionContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState(
|
||||
{
|
||||
selectedAction: type,
|
||||
},
|
||||
() => {
|
||||
const translateY = actionContainer.offsetHeight;
|
||||
|
||||
this.setState({ translateY });
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
onTileToggle = () => {
|
||||
const { onTileClick, application } = this.props;
|
||||
|
||||
onTileClick(application.clientId);
|
||||
};
|
||||
|
||||
onCollapseRest = () => {
|
||||
if (!this.props.expand && this.state.selectedAction) {
|
||||
this.setActiveAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
onActionButtonClick = (type: string | null) => () => {
|
||||
this.setActiveAction(type === this.state.selectedAction ? null : type);
|
||||
};
|
||||
|
||||
onResetSubmit = (resetClientSecret: boolean) => async () => {
|
||||
const { onResetSubmit, application } = this.props;
|
||||
|
||||
this.setState({
|
||||
isActionPerforming: true,
|
||||
});
|
||||
|
||||
await onResetSubmit(application.clientId, resetClientSecret);
|
||||
|
||||
this.setState({
|
||||
isActionPerforming: false,
|
||||
});
|
||||
this.setActiveAction(null);
|
||||
};
|
||||
|
||||
onSubmitDelete = () => {
|
||||
const { onDeleteSubmit, application } = this.props;
|
||||
|
||||
this.setState({
|
||||
isActionPerforming: true,
|
||||
});
|
||||
|
||||
onDeleteSubmit(application.clientId);
|
||||
};
|
||||
}
|
||||
112
packages/app/components/dev/apps/list/ApplicationsList.tsx
Normal file
112
packages/app/components/dev/apps/list/ApplicationsList.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import React from 'react';
|
||||
import { restoreScroll } from 'app/components/ui/scroll/scroll';
|
||||
import { FormattedMessage as Message } from 'react-intl';
|
||||
import { LinkButton } from 'app/components/ui/form';
|
||||
import { COLOR_GREEN } from 'app/components/ui';
|
||||
import { OauthAppResponse } from 'app/services/api/oauth';
|
||||
|
||||
import messages from '../ApplicationsIndex.intl.json';
|
||||
import styles from '../applicationsIndex.scss';
|
||||
import ApplicationItem from './ApplicationItem';
|
||||
|
||||
type Props = {
|
||||
applications: OauthAppResponse[];
|
||||
deleteApp: (clientId: string) => Promise<any>;
|
||||
resetApp: (clientId: string, resetClientSecret: boolean) => Promise<any>;
|
||||
resetClientId: () => void;
|
||||
clientId: string | null;
|
||||
};
|
||||
|
||||
type State = {
|
||||
expandedApp: string | null;
|
||||
};
|
||||
|
||||
export default class ApplicationsList extends React.Component<Props, State> {
|
||||
state = {
|
||||
expandedApp: null,
|
||||
};
|
||||
|
||||
appsRefs: { [key: string]: HTMLDivElement | null } = {};
|
||||
|
||||
componentDidMount() {
|
||||
this.checkForActiveApp();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.checkForActiveApp();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { applications, resetApp, deleteApp } = this.props;
|
||||
const { expandedApp } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.appsListTitleContainer}>
|
||||
<div className={styles.appsListTitle}>
|
||||
<Message {...messages.yourApplications} />
|
||||
</div>
|
||||
<LinkButton
|
||||
to="/dev/applications/new"
|
||||
data-e2e="newApp"
|
||||
label={messages.addNew}
|
||||
color={COLOR_GREEN}
|
||||
className={styles.appsListAddNewAppBtn}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.appsListContainer}>
|
||||
{applications.map(app => (
|
||||
<div
|
||||
key={app.clientId}
|
||||
ref={elem => {
|
||||
this.appsRefs[app.clientId] = elem;
|
||||
}}
|
||||
>
|
||||
<ApplicationItem
|
||||
application={app}
|
||||
expand={app.clientId === expandedApp}
|
||||
onTileClick={this.onTileClick}
|
||||
onResetSubmit={resetApp}
|
||||
onDeleteSubmit={deleteApp}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
checkForActiveApp() {
|
||||
const { applications, clientId } = this.props;
|
||||
const { expandedApp } = this.state;
|
||||
|
||||
if (
|
||||
clientId &&
|
||||
expandedApp !== clientId &&
|
||||
applications.some(app => app.clientId === clientId)
|
||||
) {
|
||||
requestAnimationFrame(() =>
|
||||
this.onTileClick(clientId, { noReset: true }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
onTileClick = (
|
||||
clientId: string,
|
||||
{ noReset = false }: { noReset?: boolean } = {},
|
||||
) => {
|
||||
const { clientId: initialClientId, resetClientId } = this.props;
|
||||
const expandedApp = this.state.expandedApp === clientId ? null : clientId;
|
||||
|
||||
if (initialClientId && noReset !== true) {
|
||||
resetClientId();
|
||||
}
|
||||
|
||||
this.setState({ expandedApp }, () => {
|
||||
if (expandedApp !== null) {
|
||||
// TODO: @SleepWalker: мб у тебя есть идея, как это сделать более правильно и менее дёргано?
|
||||
setTimeout(() => restoreScroll(this.appsRefs[clientId]), 150);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
1
packages/app/components/dev/apps/list/index.ts
Normal file
1
packages/app/components/dev/apps/list/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './ApplicationsList';
|
||||
Reference in New Issue
Block a user