mirror of
https://github.com/elyby/accounts-frontend.git
synced 2025-05-31 14:11:58 +05:30
Change prettier rules
This commit is contained in:
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"accountsForDevelopers": "Ely.by Accounts for developers",
|
||||
"accountsAllowsYouYoUseOauth2": "Ely.by Accounts service provides users with a quick and easy-to-use way to login to your site, launcher or Minecraft server via OAuth2 authorization protocol. You can find more information about integration with Ely.by Accounts in {ourDocumentation}.",
|
||||
"ourDocumentation": "our documentation",
|
||||
"ifYouHaveAnyTroubles": "If you are experiencing difficulties, you can always use {feedback}. We'll surely help you.",
|
||||
"feedback": "feedback",
|
||||
"weDontKnowAnythingAboutYou": "We don't know anything about you yet.",
|
||||
"youMustAuthToBegin": "You have to authorize to start.",
|
||||
"authorization": "Authorization",
|
||||
"youDontHaveAnyApplication": "You don't have any app registered yet.",
|
||||
"shallWeStart": "Shall we start?",
|
||||
"addNew": "Add new",
|
||||
"yourApplications": "Your applications:",
|
||||
"countUsers": "{count, plural, =0 {No users} one {# user} other {# users}}",
|
||||
"ifYouSuspectingThatSecretHasBeenCompromised": "If you are suspecting that your Client Secret has been compromised, then you may want to reset it value. It'll cause recall of the all \"access\" and \"refresh\" tokens that have been issued. You can also recall all issued tokens without changing Client Secret.",
|
||||
"revokeAllTokens": "Revoke all tokens",
|
||||
"resetClientSecret": "Reset Client Secret",
|
||||
"delete": "Delete",
|
||||
"editDescription": "{icon} Edit description",
|
||||
"allRefreshTokensWillBecomeInvalid": "All \"refresh\" tokens will become invalid and after next authorization the user will get permissions prompt.",
|
||||
"appAndAllTokenWillBeDeleted": "Application and all associated tokens will be deleted.",
|
||||
"takeCareAccessTokensInvalidation": "Take care because \"access\" tokens won't be invalidated immediately.",
|
||||
"cancel": "Cancel",
|
||||
"continue": "Continue",
|
||||
"performing": "Performing…"
|
||||
"accountsForDevelopers": "Ely.by Accounts for developers",
|
||||
"accountsAllowsYouYoUseOauth2": "Ely.by Accounts service provides users with a quick and easy-to-use way to login to your site, launcher or Minecraft server via OAuth2 authorization protocol. You can find more information about integration with Ely.by Accounts in {ourDocumentation}.",
|
||||
"ourDocumentation": "our documentation",
|
||||
"ifYouHaveAnyTroubles": "If you are experiencing difficulties, you can always use {feedback}. We'll surely help you.",
|
||||
"feedback": "feedback",
|
||||
"weDontKnowAnythingAboutYou": "We don't know anything about you yet.",
|
||||
"youMustAuthToBegin": "You have to authorize to start.",
|
||||
"authorization": "Authorization",
|
||||
"youDontHaveAnyApplication": "You don't have any app registered yet.",
|
||||
"shallWeStart": "Shall we start?",
|
||||
"addNew": "Add new",
|
||||
"yourApplications": "Your applications:",
|
||||
"countUsers": "{count, plural, =0 {No users} one {# user} other {# users}}",
|
||||
"ifYouSuspectingThatSecretHasBeenCompromised": "If you are suspecting that your Client Secret has been compromised, then you may want to reset it value. It'll cause recall of the all \"access\" and \"refresh\" tokens that have been issued. You can also recall all issued tokens without changing Client Secret.",
|
||||
"revokeAllTokens": "Revoke all tokens",
|
||||
"resetClientSecret": "Reset Client Secret",
|
||||
"delete": "Delete",
|
||||
"editDescription": "{icon} Edit description",
|
||||
"allRefreshTokensWillBecomeInvalid": "All \"refresh\" tokens will become invalid and after next authorization the user will get permissions prompt.",
|
||||
"appAndAllTokenWillBeDeleted": "Application and all associated tokens will be deleted.",
|
||||
"takeCareAccessTokensInvalidation": "Take care because \"access\" tokens won't be invalidated immediately.",
|
||||
"cancel": "Cancel",
|
||||
"continue": "Continue",
|
||||
"performing": "Performing…"
|
||||
}
|
||||
|
@@ -15,144 +15,133 @@ import toolsIcon from './icons/tools.svg';
|
||||
import ApplicationsList from './list';
|
||||
|
||||
type Props = {
|
||||
clientId: string | null;
|
||||
resetClientId: () => void; // notify parent to remove clientId from current location.href
|
||||
displayForGuest: boolean;
|
||||
applications: Array<OauthAppResponse>;
|
||||
isLoading: boolean;
|
||||
deleteApp: (clientId: string) => Promise<any>;
|
||||
resetApp: (clientId: string, resetClientSecret: boolean) => Promise<any>;
|
||||
clientId: string | null;
|
||||
resetClientId: () => void; // notify parent to remove clientId from current location.href
|
||||
displayForGuest: boolean;
|
||||
applications: Array<OauthAppResponse>;
|
||||
isLoading: boolean;
|
||||
deleteApp: (clientId: string) => Promise<any>;
|
||||
resetApp: (clientId: string, resetClientSecret: boolean) => Promise<any>;
|
||||
};
|
||||
|
||||
export default class ApplicationsIndex extends React.Component<Props> {
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.welcomeContainer}>
|
||||
<Message {...messages.accountsForDevelopers}>
|
||||
{(pageTitle: string) => (
|
||||
<h2 className={styles.welcomeTitle}>
|
||||
<Helmet title={pageTitle} />
|
||||
{pageTitle}
|
||||
</h2>
|
||||
)}
|
||||
</Message>
|
||||
<div className={styles.welcomeTitleDelimiter} />
|
||||
<div className={styles.welcomeParagraph}>
|
||||
<Message
|
||||
{...messages.accountsAllowsYouYoUseOauth2}
|
||||
values={{
|
||||
ourDocumentation: (
|
||||
<a href="https://docs.ely.by/en/oauth.html" target="_blank">
|
||||
<Message {...messages.ourDocumentation} />
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.welcomeParagraph}>
|
||||
<Message
|
||||
{...messages.ifYouHaveAnyTroubles}
|
||||
values={{
|
||||
feedback: (
|
||||
<ContactLink>
|
||||
<Message {...messages.feedback} />
|
||||
</ContactLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.welcomeContainer}>
|
||||
<Message {...messages.accountsForDevelopers}>
|
||||
{(pageTitle: string) => (
|
||||
<h2 className={styles.welcomeTitle}>
|
||||
<Helmet title={pageTitle} />
|
||||
{pageTitle}
|
||||
</h2>
|
||||
)}
|
||||
</Message>
|
||||
<div className={styles.welcomeTitleDelimiter} />
|
||||
<div className={styles.welcomeParagraph}>
|
||||
<Message
|
||||
{...messages.accountsAllowsYouYoUseOauth2}
|
||||
values={{
|
||||
ourDocumentation: (
|
||||
<a href="https://docs.ely.by/en/oauth.html" target="_blank">
|
||||
<Message {...messages.ourDocumentation} />
|
||||
</a>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.welcomeParagraph}>
|
||||
<Message
|
||||
{...messages.ifYouHaveAnyTroubles}
|
||||
values={{
|
||||
feedback: (
|
||||
<ContactLink>
|
||||
<Message {...messages.feedback} />
|
||||
</ContactLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.getContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getContent() {
|
||||
const {
|
||||
displayForGuest,
|
||||
applications,
|
||||
isLoading,
|
||||
resetApp,
|
||||
deleteApp,
|
||||
clientId,
|
||||
resetClientId,
|
||||
} = this.props;
|
||||
|
||||
if (applications.length > 0) {
|
||||
return (
|
||||
<ApplicationsList
|
||||
applications={applications}
|
||||
resetApp={resetApp}
|
||||
deleteApp={deleteApp}
|
||||
clientId={clientId}
|
||||
resetClientId={resetClientId}
|
||||
/>
|
||||
);
|
||||
{this.getContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (displayForGuest) {
|
||||
return <Guest />;
|
||||
}
|
||||
getContent() {
|
||||
const { displayForGuest, applications, isLoading, resetApp, deleteApp, clientId, resetClientId } = this.props;
|
||||
|
||||
return <Loader noApps={!isLoading} />;
|
||||
}
|
||||
if (applications.length > 0) {
|
||||
return (
|
||||
<ApplicationsList
|
||||
applications={applications}
|
||||
resetApp={resetApp}
|
||||
deleteApp={deleteApp}
|
||||
clientId={clientId}
|
||||
resetClientId={resetClientId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (displayForGuest) {
|
||||
return <Guest />;
|
||||
}
|
||||
|
||||
return <Loader noApps={!isLoading} />;
|
||||
}
|
||||
}
|
||||
|
||||
function Loader({ noApps }: { noApps: boolean }) {
|
||||
return (
|
||||
<div className={styles.emptyState} data-e2e={noApps ? 'noApps' : 'loading'}>
|
||||
<img
|
||||
src={noApps ? cubeIcon : loadingCubeIcon}
|
||||
className={styles.emptyStateIcon}
|
||||
/>
|
||||
return (
|
||||
<div className={styles.emptyState} data-e2e={noApps ? 'noApps' : 'loading'}>
|
||||
<img src={noApps ? cubeIcon : loadingCubeIcon} className={styles.emptyStateIcon} />
|
||||
|
||||
<div
|
||||
className={clsx(styles.noAppsContainer, {
|
||||
[styles.noAppsAnimating]: noApps,
|
||||
})}
|
||||
>
|
||||
<div className={styles.emptyStateText}>
|
||||
<div>
|
||||
<Message {...messages.youDontHaveAnyApplication} />
|
||||
</div>
|
||||
<div>
|
||||
<Message {...messages.shallWeStart} />
|
||||
</div>
|
||||
<div
|
||||
className={clsx(styles.noAppsContainer, {
|
||||
[styles.noAppsAnimating]: noApps,
|
||||
})}
|
||||
>
|
||||
<div className={styles.emptyStateText}>
|
||||
<div>
|
||||
<Message {...messages.youDontHaveAnyApplication} />
|
||||
</div>
|
||||
<div>
|
||||
<Message {...messages.shallWeStart} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LinkButton
|
||||
to="/dev/applications/new"
|
||||
data-e2e="newApp"
|
||||
label={messages.addNew}
|
||||
color={COLOR_GREEN}
|
||||
className={styles.emptyStateActionButton}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LinkButton
|
||||
to="/dev/applications/new"
|
||||
data-e2e="newApp"
|
||||
label={messages.addNew}
|
||||
color={COLOR_GREEN}
|
||||
className={styles.emptyStateActionButton}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
function Guest() {
|
||||
return (
|
||||
<div className={styles.emptyState}>
|
||||
<img src={toolsIcon} className={styles.emptyStateIcon} />
|
||||
<div className={styles.emptyStateText}>
|
||||
<div>
|
||||
<Message {...messages.weDontKnowAnythingAboutYou} />
|
||||
</div>
|
||||
<div>
|
||||
<Message {...messages.youMustAuthToBegin} />
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
<div className={styles.emptyState}>
|
||||
<img src={toolsIcon} className={styles.emptyStateIcon} />
|
||||
<div className={styles.emptyStateText}>
|
||||
<div>
|
||||
<Message {...messages.weDontKnowAnythingAboutYou} />
|
||||
</div>
|
||||
<div>
|
||||
<Message {...messages.youMustAuthToBegin} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LinkButton
|
||||
to="/login"
|
||||
label={messages.authorization}
|
||||
color={COLOR_BLUE}
|
||||
className={styles.emptyStateActionButton}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
<LinkButton
|
||||
to="/login"
|
||||
label={messages.authorization}
|
||||
color={COLOR_BLUE}
|
||||
className={styles.emptyStateActionButton}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -7,94 +7,85 @@ import { ThunkAction } from 'app/reducers';
|
||||
import { Apps } from './reducer';
|
||||
|
||||
interface SetAvailableAction extends ReduxAction {
|
||||
type: 'apps:setAvailable';
|
||||
payload: Array<OauthAppResponse>;
|
||||
type: 'apps:setAvailable';
|
||||
payload: Array<OauthAppResponse>;
|
||||
}
|
||||
|
||||
export function setAppsList(apps: Array<OauthAppResponse>): SetAvailableAction {
|
||||
return {
|
||||
type: 'apps:setAvailable',
|
||||
payload: apps,
|
||||
};
|
||||
return {
|
||||
type: 'apps:setAvailable',
|
||||
payload: apps,
|
||||
};
|
||||
}
|
||||
|
||||
export function getApp(
|
||||
state: { apps: Apps },
|
||||
clientId: string,
|
||||
): OauthAppResponse | null {
|
||||
return state.apps.available.find((app) => app.clientId === clientId) || null;
|
||||
export function getApp(state: { apps: Apps }, clientId: string): OauthAppResponse | null {
|
||||
return state.apps.available.find((app) => app.clientId === clientId) || null;
|
||||
}
|
||||
|
||||
export function fetchApp(clientId: string): ThunkAction<Promise<void>> {
|
||||
return async (dispatch) => {
|
||||
const app = await oauth.getApp(clientId);
|
||||
return async (dispatch) => {
|
||||
const app = await oauth.getApp(clientId);
|
||||
|
||||
dispatch(addApp(app));
|
||||
};
|
||||
dispatch(addApp(app));
|
||||
};
|
||||
}
|
||||
|
||||
interface AddAppAction extends ReduxAction {
|
||||
type: 'apps:addApp';
|
||||
payload: OauthAppResponse;
|
||||
type: 'apps:addApp';
|
||||
payload: OauthAppResponse;
|
||||
}
|
||||
|
||||
function addApp(app: OauthAppResponse): AddAppAction {
|
||||
return {
|
||||
type: 'apps:addApp',
|
||||
payload: app,
|
||||
};
|
||||
return {
|
||||
type: 'apps:addApp',
|
||||
payload: app,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchAvailableApps() {
|
||||
return async (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => { user: User },
|
||||
): Promise<void> => {
|
||||
const { id } = getState().user;
|
||||
return async (dispatch: Dispatch<any>, getState: () => { user: User }): Promise<void> => {
|
||||
const { id } = getState().user;
|
||||
|
||||
if (!id) {
|
||||
dispatch(setAppsList([]));
|
||||
if (!id) {
|
||||
dispatch(setAppsList([]));
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const apps = await oauth.getAppsByUser(id);
|
||||
const apps = await oauth.getAppsByUser(id);
|
||||
|
||||
dispatch(setAppsList(apps));
|
||||
};
|
||||
dispatch(setAppsList(apps));
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteApp(clientId: string) {
|
||||
return async (dispatch: Dispatch<any>): Promise<void> => {
|
||||
await oauth.delete(clientId);
|
||||
return async (dispatch: Dispatch<any>): Promise<void> => {
|
||||
await oauth.delete(clientId);
|
||||
|
||||
dispatch(createDeleteAppAction(clientId));
|
||||
};
|
||||
dispatch(createDeleteAppAction(clientId));
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteAppAction extends ReduxAction {
|
||||
type: 'apps:deleteApp';
|
||||
payload: string;
|
||||
type: 'apps:deleteApp';
|
||||
payload: string;
|
||||
}
|
||||
|
||||
function createDeleteAppAction(clientId: string): DeleteAppAction {
|
||||
return {
|
||||
type: 'apps:deleteApp',
|
||||
payload: clientId,
|
||||
};
|
||||
return {
|
||||
type: 'apps:deleteApp',
|
||||
payload: clientId,
|
||||
};
|
||||
}
|
||||
|
||||
export function resetApp(
|
||||
clientId: string,
|
||||
resetSecret: boolean,
|
||||
): ThunkAction<Promise<void>> {
|
||||
return async (dispatch) => {
|
||||
const { data: app } = await oauth.reset(clientId, resetSecret);
|
||||
export function resetApp(clientId: string, resetSecret: boolean): ThunkAction<Promise<void>> {
|
||||
return async (dispatch) => {
|
||||
const { data: app } = await oauth.reset(clientId, resetSecret);
|
||||
|
||||
if (resetSecret) {
|
||||
dispatch(addApp(app));
|
||||
}
|
||||
};
|
||||
if (resetSecret) {
|
||||
dispatch(addApp(app));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export type Action = SetAvailableAction | DeleteAppAction | AddAppAction;
|
||||
|
@@ -1,20 +1,20 @@
|
||||
{
|
||||
"creatingApplication": "Creating an application",
|
||||
"website": "Web site",
|
||||
"minecraftServer": "Minecraft server",
|
||||
"toDisplayRegistrationFormChooseType": "To display registration form for a new application choose necessary type.",
|
||||
"applicationName": "Application name:",
|
||||
"appDescriptionWillBeAlsoVisibleOnOauthPage": "Application's description will be displayed at the authorization page too. It isn't a required field. In authorization process the value may be overridden.",
|
||||
"description": "Description:",
|
||||
"websiteLinkWillBeUsedAsAdditionalId": "Site's link is optional, but it can be used as an additional identifier of the application.",
|
||||
"websiteLink": "Website link:",
|
||||
"redirectUriLimitsAllowableBaseAddress": "Redirection URI (redirectUri) determines a base address, that user will be allowed to be redirected to after authorization. In order to improve security it's better to use the whole path instead of just a domain name. For example: https://example.com/oauth/ely.",
|
||||
"redirectUri": "Redirect URI:",
|
||||
"createApplication": "Create application",
|
||||
"serverName": "Server name:",
|
||||
"ipAddressIsOptionButPreferable": "IP address is optional, but is very preferable. It might become handy in case of we suddenly decide to play on your server with the entire band (=",
|
||||
"serverIp": "Server IP:",
|
||||
"youCanAlsoSpecifyServerSite": "You also can specify either server's site URL or its community in a social network.",
|
||||
"updatingApplication": "Updating an application",
|
||||
"updateApplication": "Update application"
|
||||
"creatingApplication": "Creating an application",
|
||||
"website": "Web site",
|
||||
"minecraftServer": "Minecraft server",
|
||||
"toDisplayRegistrationFormChooseType": "To display registration form for a new application choose necessary type.",
|
||||
"applicationName": "Application name:",
|
||||
"appDescriptionWillBeAlsoVisibleOnOauthPage": "Application's description will be displayed at the authorization page too. It isn't a required field. In authorization process the value may be overridden.",
|
||||
"description": "Description:",
|
||||
"websiteLinkWillBeUsedAsAdditionalId": "Site's link is optional, but it can be used as an additional identifier of the application.",
|
||||
"websiteLink": "Website link:",
|
||||
"redirectUriLimitsAllowableBaseAddress": "Redirection URI (redirectUri) determines a base address, that user will be allowed to be redirected to after authorization. In order to improve security it's better to use the whole path instead of just a domain name. For example: https://example.com/oauth/ely.",
|
||||
"redirectUri": "Redirect URI:",
|
||||
"createApplication": "Create application",
|
||||
"serverName": "Server name:",
|
||||
"ipAddressIsOptionButPreferable": "IP address is optional, but is very preferable. It might become handy in case of we suddenly decide to play on your server with the entire band (=",
|
||||
"serverIp": "Server IP:",
|
||||
"youCanAlsoSpecifyServerSite": "You also can specify either server's site URL or its community in a social network.",
|
||||
"updatingApplication": "Updating an application",
|
||||
"updateApplication": "Update application"
|
||||
}
|
||||
|
@@ -7,10 +7,7 @@ import { ApplicationType } from 'app/components/dev/apps';
|
||||
import { Form, FormModel, Button } from 'app/components/ui/form';
|
||||
import { BackButton } from 'app/components/profile/ProfileForm';
|
||||
import { COLOR_GREEN } from 'app/components/ui';
|
||||
import {
|
||||
TYPE_APPLICATION,
|
||||
TYPE_MINECRAFT_SERVER,
|
||||
} from 'app/components/dev/apps';
|
||||
import { TYPE_APPLICATION, TYPE_MINECRAFT_SERVER } from 'app/components/dev/apps';
|
||||
import styles from 'app/components/profile/profileForm.scss';
|
||||
import logger from 'app/services/logger';
|
||||
import messages from './ApplicationForm.intl.json';
|
||||
@@ -20,125 +17,116 @@ import WebsiteType from './WebsiteType';
|
||||
import MinecraftServerType from './MinecraftServerType';
|
||||
|
||||
type TypeToForm = Record<
|
||||
ApplicationType,
|
||||
{
|
||||
label: MessageDescriptor;
|
||||
component: React.ComponentType<any>;
|
||||
}
|
||||
ApplicationType,
|
||||
{
|
||||
label: MessageDescriptor;
|
||||
component: React.ComponentType<any>;
|
||||
}
|
||||
>;
|
||||
|
||||
const typeToForm: TypeToForm = {
|
||||
[TYPE_APPLICATION]: {
|
||||
label: messages.website,
|
||||
component: WebsiteType,
|
||||
},
|
||||
[TYPE_MINECRAFT_SERVER]: {
|
||||
label: messages.minecraftServer,
|
||||
component: MinecraftServerType,
|
||||
},
|
||||
[TYPE_APPLICATION]: {
|
||||
label: messages.website,
|
||||
component: WebsiteType,
|
||||
},
|
||||
[TYPE_MINECRAFT_SERVER]: {
|
||||
label: messages.minecraftServer,
|
||||
component: MinecraftServerType,
|
||||
},
|
||||
};
|
||||
|
||||
type TypeToLabel = Record<ApplicationType, MessageDescriptor>;
|
||||
|
||||
const typeToLabel: TypeToLabel = ((Object.keys(typeToForm) as unknown) as Array<
|
||||
ApplicationType
|
||||
>).reduce((result, key) => {
|
||||
result[key] = typeToForm[key].label;
|
||||
const typeToLabel: TypeToLabel = ((Object.keys(typeToForm) as unknown) as Array<ApplicationType>).reduce(
|
||||
(result, key) => {
|
||||
result[key] = typeToForm[key].label;
|
||||
|
||||
return result;
|
||||
}, {} as TypeToLabel);
|
||||
return result;
|
||||
},
|
||||
{} as TypeToLabel,
|
||||
);
|
||||
|
||||
export default class ApplicationForm extends React.Component<{
|
||||
app: OauthAppResponse;
|
||||
form: FormModel;
|
||||
displayTypeSwitcher?: boolean;
|
||||
type: ApplicationType | null;
|
||||
setType: (type: ApplicationType) => void;
|
||||
onSubmit: (form: FormModel) => Promise<void>;
|
||||
app: OauthAppResponse;
|
||||
form: FormModel;
|
||||
displayTypeSwitcher?: boolean;
|
||||
type: ApplicationType | null;
|
||||
setType: (type: ApplicationType) => void;
|
||||
onSubmit: (form: FormModel) => Promise<void>;
|
||||
}> {
|
||||
static defaultProps = {
|
||||
setType: () => {},
|
||||
};
|
||||
static defaultProps = {
|
||||
setType: () => {},
|
||||
};
|
||||
|
||||
render() {
|
||||
const { type, setType, form, displayTypeSwitcher, app } = this.props;
|
||||
const { component: FormComponent } = (type && typeToForm[type]) || {};
|
||||
const isUpdate = app.clientId !== '';
|
||||
render() {
|
||||
const { type, setType, form, displayTypeSwitcher, app } = this.props;
|
||||
const { component: FormComponent } = (type && typeToForm[type]) || {};
|
||||
const isUpdate = app.clientId !== '';
|
||||
|
||||
return (
|
||||
<Form form={form} onSubmit={this.onFormSubmit}>
|
||||
<div className={styles.contentWithBackButton}>
|
||||
<BackButton to="/dev/applications" />
|
||||
return (
|
||||
<Form form={form} onSubmit={this.onFormSubmit}>
|
||||
<div className={styles.contentWithBackButton}>
|
||||
<BackButton to="/dev/applications" />
|
||||
|
||||
<div className={styles.form}>
|
||||
<div className={styles.formBody}>
|
||||
<Message
|
||||
{...(isUpdate
|
||||
? messages.updatingApplication
|
||||
: messages.creatingApplication)}
|
||||
>
|
||||
{(pageTitle: string) => (
|
||||
<h3 className={styles.title}>
|
||||
<Helmet title={pageTitle} />
|
||||
{pageTitle}
|
||||
</h3>
|
||||
)}
|
||||
</Message>
|
||||
<div className={styles.form}>
|
||||
<div className={styles.formBody}>
|
||||
<Message {...(isUpdate ? messages.updatingApplication : messages.creatingApplication)}>
|
||||
{(pageTitle: string) => (
|
||||
<h3 className={styles.title}>
|
||||
<Helmet title={pageTitle} />
|
||||
{pageTitle}
|
||||
</h3>
|
||||
)}
|
||||
</Message>
|
||||
|
||||
{displayTypeSwitcher && (
|
||||
<div className={styles.formRow}>
|
||||
<ApplicationTypeSwitcher
|
||||
selectedType={type}
|
||||
setType={setType}
|
||||
appTypes={typeToLabel}
|
||||
/>
|
||||
{displayTypeSwitcher && (
|
||||
<div className={styles.formRow}>
|
||||
<ApplicationTypeSwitcher
|
||||
selectedType={type}
|
||||
setType={setType}
|
||||
appTypes={typeToLabel}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{FormComponent ? (
|
||||
<FormComponent form={form} app={app} />
|
||||
) : (
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.toDisplayRegistrationFormChooseType} />
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!!FormComponent && (
|
||||
<Button
|
||||
color={COLOR_GREEN}
|
||||
block
|
||||
label={isUpdate ? messages.updateApplication : messages.createApplication}
|
||||
type="submit"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{FormComponent ? (
|
||||
<FormComponent form={form} app={app} />
|
||||
) : (
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message
|
||||
{...messages.toDisplayRegistrationFormChooseType}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!!FormComponent && (
|
||||
<Button
|
||||
color={COLOR_GREEN}
|
||||
block
|
||||
label={
|
||||
isUpdate
|
||||
? messages.updateApplication
|
||||
: messages.createApplication
|
||||
}
|
||||
type="submit"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
onFormSubmit = async () => {
|
||||
const { form } = this.props;
|
||||
|
||||
try {
|
||||
await this.props.onSubmit(form);
|
||||
} catch (resp) {
|
||||
if (resp.errors) {
|
||||
form.setErrors(resp.errors);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.unexpected(new Error('Error submitting application form'), resp);
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
onFormSubmit = async () => {
|
||||
const { form } = this.props;
|
||||
|
||||
try {
|
||||
await this.props.onSubmit(form);
|
||||
} catch (resp) {
|
||||
if (resp.errors) {
|
||||
form.setErrors(resp.errors);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
logger.unexpected(new Error('Error submitting application form'), resp);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -7,31 +7,25 @@ import { Radio } from 'app/components/ui/form';
|
||||
import styles from './applicationTypeSwitcher.scss';
|
||||
|
||||
interface Props {
|
||||
appTypes: Record<ApplicationType, MessageDescriptor>;
|
||||
selectedType: ApplicationType | null;
|
||||
setType: (type: ApplicationType) => void;
|
||||
appTypes: Record<ApplicationType, MessageDescriptor>;
|
||||
selectedType: ApplicationType | null;
|
||||
setType: (type: ApplicationType) => void;
|
||||
}
|
||||
|
||||
const ApplicationTypeSwitcher: ComponentType<Props> = ({
|
||||
appTypes,
|
||||
selectedType,
|
||||
setType,
|
||||
}) => (
|
||||
<div>
|
||||
{((Object.keys(appTypes) as unknown) as Array<ApplicationType>).map(
|
||||
(type) => (
|
||||
<div className={styles.radioContainer} key={type}>
|
||||
<Radio
|
||||
onChange={() => setType(type)}
|
||||
skin={SKIN_LIGHT}
|
||||
label={appTypes[type]}
|
||||
value={type}
|
||||
checked={selectedType === type}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
const ApplicationTypeSwitcher: ComponentType<Props> = ({ appTypes, selectedType, setType }) => (
|
||||
<div>
|
||||
{((Object.keys(appTypes) as unknown) as Array<ApplicationType>).map((type) => (
|
||||
<div className={styles.radioContainer} key={type}>
|
||||
<Radio
|
||||
onChange={() => setType(type)}
|
||||
skin={SKIN_LIGHT}
|
||||
label={appTypes[type]}
|
||||
value={type}
|
||||
checked={selectedType === type}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ApplicationTypeSwitcher;
|
||||
|
@@ -8,50 +8,50 @@ import styles from 'app/components/profile/profileForm.scss';
|
||||
import messages from './ApplicationForm.intl.json';
|
||||
|
||||
interface Props {
|
||||
form: FormModel;
|
||||
app: OauthAppResponse;
|
||||
form: FormModel;
|
||||
app: OauthAppResponse;
|
||||
}
|
||||
|
||||
const MinecraftServerType: ComponentType<Props> = ({ form, app }) => (
|
||||
<div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('name')}
|
||||
label={messages.serverName}
|
||||
defaultValue={app.name}
|
||||
required
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('name')}
|
||||
label={messages.serverName}
|
||||
defaultValue={app.name}
|
||||
required
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.ipAddressIsOptionButPreferable} />
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('minecraftServerIp')}
|
||||
label={messages.serverIp}
|
||||
defaultValue={app.minecraftServerIp}
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.ipAddressIsOptionButPreferable} />
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('minecraftServerIp')}
|
||||
label={messages.serverIp}
|
||||
defaultValue={app.minecraftServerIp}
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.youCanAlsoSpecifyServerSite} />
|
||||
</p>
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.youCanAlsoSpecifyServerSite} />
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('websiteUrl')}
|
||||
label={messages.websiteLink}
|
||||
defaultValue={app.websiteUrl}
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('websiteUrl')}
|
||||
label={messages.websiteLink}
|
||||
defaultValue={app.websiteUrl}
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default MinecraftServerType;
|
||||
|
@@ -8,66 +8,66 @@ import styles from 'app/components/profile/profileForm.scss';
|
||||
import messages from './ApplicationForm.intl.json';
|
||||
|
||||
interface Props {
|
||||
form: FormModel;
|
||||
app: OauthAppResponse;
|
||||
form: FormModel;
|
||||
app: OauthAppResponse;
|
||||
}
|
||||
|
||||
const WebsiteType: ComponentType<Props> = ({ form, app }) => (
|
||||
<div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('name')}
|
||||
label={messages.applicationName}
|
||||
defaultValue={app.name}
|
||||
required
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('name')}
|
||||
label={messages.applicationName}
|
||||
defaultValue={app.name}
|
||||
required
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.appDescriptionWillBeAlsoVisibleOnOauthPage} />
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<TextArea
|
||||
{...form.bindField('description')}
|
||||
label={messages.description}
|
||||
defaultValue={app.description}
|
||||
skin={SKIN_LIGHT}
|
||||
minRows={3}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.appDescriptionWillBeAlsoVisibleOnOauthPage} />
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<TextArea
|
||||
{...form.bindField('description')}
|
||||
label={messages.description}
|
||||
defaultValue={app.description}
|
||||
skin={SKIN_LIGHT}
|
||||
minRows={3}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.websiteLinkWillBeUsedAsAdditionalId} />
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('websiteUrl')}
|
||||
label={messages.websiteLink}
|
||||
defaultValue={app.websiteUrl}
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.websiteLinkWillBeUsedAsAdditionalId} />
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('websiteUrl')}
|
||||
label={messages.websiteLink}
|
||||
defaultValue={app.websiteUrl}
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.redirectUriLimitsAllowableBaseAddress} />
|
||||
</p>
|
||||
<div className={styles.formRow}>
|
||||
<p className={styles.description}>
|
||||
<Message {...messages.redirectUriLimitsAllowableBaseAddress} />
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('redirectUri')}
|
||||
label={messages.redirectUri}
|
||||
defaultValue={app.redirectUri}
|
||||
required
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.formRow}>
|
||||
<Input
|
||||
{...form.bindField('redirectUri')}
|
||||
label={messages.redirectUri}
|
||||
defaultValue={app.redirectUri}
|
||||
required
|
||||
skin={SKIN_LIGHT}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default WebsiteType;
|
||||
|
@@ -1,3 +1,3 @@
|
||||
.radioContainer {
|
||||
margin-top: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
@@ -2,261 +2,261 @@
|
||||
@import '~app/components/ui/colors.scss';
|
||||
|
||||
.container {
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-bottom: 10px solid #ddd8ce;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-bottom: 10px solid #ddd8ce;
|
||||
|
||||
@media (max-width: 540px) {
|
||||
margin: 0 20px;
|
||||
}
|
||||
@media (max-width: 540px) {
|
||||
margin: 0 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.welcomeContainer {
|
||||
padding: 30px;
|
||||
background: #f5f5f5;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
padding: 30px;
|
||||
background: #f5f5f5;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
}
|
||||
|
||||
.welcomeTitle {
|
||||
font-size: 30px;
|
||||
font-family: $font-family-title;
|
||||
max-width: 245px;
|
||||
margin: 0 auto 15px;
|
||||
line-height: 1.2;
|
||||
font-size: 30px;
|
||||
font-family: $font-family-title;
|
||||
max-width: 245px;
|
||||
margin: 0 auto 15px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.welcomeTitleDelimiter {
|
||||
width: 86px;
|
||||
height: 3px;
|
||||
background: $green;
|
||||
margin: 0 auto 15px;
|
||||
width: 86px;
|
||||
height: 3px;
|
||||
background: $green;
|
||||
margin: 0 auto 15px;
|
||||
}
|
||||
|
||||
.welcomeParagraph {
|
||||
color: #666666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.3;
|
||||
color: #666666;
|
||||
font-size: 14px;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.3;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.emptyState {
|
||||
padding: 30px 30px 50px;
|
||||
text-align: center;
|
||||
padding: 30px 30px 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.emptyStateIcon {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin-bottom: 20px;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@mixin emptyStateAnimation($order) {
|
||||
animation: slide-in-bottom 1s // Total animation time
|
||||
0.2s + 0.2s * $order // Increase each next element delay
|
||||
cubic-bezier(0.075, 0.82, 0.165, 1) // easeOutCirc
|
||||
both;
|
||||
animation: slide-in-bottom 1s // Total animation time
|
||||
0.2s + 0.2s * $order // Increase each next element delay
|
||||
cubic-bezier(0.075, 0.82, 0.165, 1) // easeOutCirc
|
||||
both;
|
||||
}
|
||||
|
||||
.emptyStateText {
|
||||
font-family: $font-family-title;
|
||||
color: #666666;
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
line-height: 20px;
|
||||
font-family: $font-family-title;
|
||||
color: #666666;
|
||||
font-size: 16px;
|
||||
margin-bottom: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.noAppsContainer {
|
||||
visibility: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.noAppsAnimating {
|
||||
visibility: visible;
|
||||
visibility: visible;
|
||||
|
||||
.emptyStateText {
|
||||
> div {
|
||||
&:nth-child(1) {
|
||||
@include emptyStateAnimation(0);
|
||||
}
|
||||
.emptyStateText {
|
||||
> div {
|
||||
&:nth-child(1) {
|
||||
@include emptyStateAnimation(0);
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
@include emptyStateAnimation(1);
|
||||
}
|
||||
&:nth-child(2) {
|
||||
@include emptyStateAnimation(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emptyStateActionButton {
|
||||
@include emptyStateAnimation(2);
|
||||
}
|
||||
.emptyStateActionButton {
|
||||
@include emptyStateAnimation(2);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-in-bottom {
|
||||
0% {
|
||||
transform: translateY(50px);
|
||||
opacity: 0;
|
||||
}
|
||||
0% {
|
||||
transform: translateY(50px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.appsListTitleContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px 30px;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px 30px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.appsListTitle {
|
||||
font-family: $font-family-title;
|
||||
font-size: 24px;
|
||||
flex-grow: 1;
|
||||
font-family: $font-family-title;
|
||||
font-size: 24px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.appsListAddNewAppBtn {
|
||||
}
|
||||
|
||||
.appsListContainer {
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.appItemContainer {
|
||||
border-bottom: 1px solid #eee;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.appItemTile {
|
||||
padding: 15px 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.25s;
|
||||
padding: 15px 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
.appTileTitle {
|
||||
flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.appName {
|
||||
font-family: $font-family-title;
|
||||
font-size: 24px;
|
||||
font-family: $font-family-title;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.appStats {
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.appItemToggle {
|
||||
}
|
||||
|
||||
.appItemToggleIcon {
|
||||
composes: arrowRight from '~app/components/ui/icons.scss';
|
||||
composes: arrowRight from '~app/components/ui/icons.scss';
|
||||
|
||||
position: relative;
|
||||
left: 0;
|
||||
position: relative;
|
||||
left: 0;
|
||||
|
||||
font-size: 28px;
|
||||
color: #ebe8e1;
|
||||
font-size: 28px;
|
||||
color: #ebe8e1;
|
||||
|
||||
transition: 0.25s;
|
||||
transition: 0.25s;
|
||||
|
||||
.appItemTile:hover & {
|
||||
color: #777;
|
||||
}
|
||||
.appItemTile:hover & {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.appExpanded & {
|
||||
color: #777;
|
||||
transform: rotate(360deg) !important; // Prevent it from hover rotating
|
||||
}
|
||||
.appExpanded & {
|
||||
color: #777;
|
||||
transform: rotate(360deg) !important; // Prevent it from hover rotating
|
||||
}
|
||||
}
|
||||
|
||||
$appDetailsContainerRightLeftPadding: 30px;
|
||||
|
||||
.appDetailsContainer {
|
||||
background: #f5f5f5;
|
||||
border-top: 1px solid #eee;
|
||||
padding: 5px $appDetailsContainerRightLeftPadding;
|
||||
position: relative;
|
||||
transition: transform 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
background: #f5f5f5;
|
||||
border-top: 1px solid #eee;
|
||||
padding: 5px $appDetailsContainerRightLeftPadding;
|
||||
position: relative;
|
||||
transition: transform 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||
}
|
||||
|
||||
.appDetailsInfoField {
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.editAppLink {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 0;
|
||||
|
||||
font-size: 12px;
|
||||
color: #9a9a9a;
|
||||
border-bottom: 0;
|
||||
font-size: 12px;
|
||||
color: #9a9a9a;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.pencilIcon {
|
||||
composes: pencil from '~app/components/ui/icons.scss';
|
||||
composes: pencil from '~app/components/ui/icons.scss';
|
||||
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
bottom: 2px;
|
||||
}
|
||||
|
||||
.appDetailsDescription {
|
||||
font-size: 12px;
|
||||
color: #9a9a9a;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 20px;
|
||||
font-size: 12px;
|
||||
color: #9a9a9a;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.appActionsButtons {
|
||||
}
|
||||
|
||||
.appActionButton {
|
||||
margin: 0 10px 10px 0;
|
||||
margin: 0 10px 10px 0;
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.appActionContainer {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
padding: 0 $appDetailsContainerRightLeftPadding;
|
||||
background: #f5f5f5;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
padding: 0 $appDetailsContainerRightLeftPadding;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.appActionDescription {
|
||||
composes: appDetailsDescription;
|
||||
composes: appDetailsDescription;
|
||||
|
||||
margin-top: 6px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.continueActionButtonWrapper {
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.continueActionLink {
|
||||
composes: textLink from '~app/index.scss';
|
||||
composes: textLink from '~app/index.scss';
|
||||
|
||||
font-family: $font-family-title;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-family: $font-family-title;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.performingAction {
|
||||
font-family: $font-family-title;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-family: $font-family-title;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
@@ -14,292 +14,273 @@ 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,
|
||||
},
|
||||
{
|
||||
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;
|
||||
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
|
||||
{
|
||||
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,
|
||||
};
|
||||
state: State = {
|
||||
selectedAction: null,
|
||||
isActionPerforming: false,
|
||||
translateY: 0,
|
||||
detailsHeight: 0,
|
||||
};
|
||||
|
||||
actionContainer: HTMLDivElement | null;
|
||||
actionContainer: HTMLDivElement | null;
|
||||
|
||||
render() {
|
||||
const { application: app, expand } = this.props;
|
||||
const { selectedAction, translateY } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(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}
|
||||
data-testid="client-secret"
|
||||
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>
|
||||
render() {
|
||||
const { application: app, expand } = this.props;
|
||||
const { selectedAction, translateY } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.appActionContainer}
|
||||
ref={(el) => {
|
||||
this.actionContainer = el;
|
||||
}}
|
||||
className={clsx(styles.appItemContainer, {
|
||||
[styles.appExpanded]: expand,
|
||||
})}
|
||||
data-e2e="appItem"
|
||||
data-e2e-app-name={app.name}
|
||||
>
|
||||
{this.getActionContent()}
|
||||
</div>
|
||||
</div>
|
||||
</Collapse>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
<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>
|
||||
|
||||
getActionContent() {
|
||||
const { selectedAction, isActionPerforming } = this.state;
|
||||
<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>
|
||||
|
||||
switch (selectedAction) {
|
||||
case ACTION_REVOKE_TOKENS:
|
||||
case ACTION_RESET_SECRET:
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.appActionDescription}>
|
||||
<Message {...messages.allRefreshTokensWillBecomeInvalid} />{' '}
|
||||
<Message {...messages.takeCareAccessTokensInvalidation} />
|
||||
<div className={styles.appDetailsInfoField}>
|
||||
<Input
|
||||
label="Client Secret:"
|
||||
skin={SKIN_LIGHT}
|
||||
disabled
|
||||
value={app.clientSecret}
|
||||
data-testid="client-secret"
|
||||
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>
|
||||
<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}
|
||||
data-testid="delete-app"
|
||||
small
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</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}
|
||||
data-testid="delete-app"
|
||||
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 });
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
onTileToggle = () => {
|
||||
const { onTileClick, application } = this.props;
|
||||
|
||||
setActiveAction = (type: string | null) => {
|
||||
const { actionContainer } = this;
|
||||
onTileClick(application.clientId);
|
||||
};
|
||||
|
||||
if (!actionContainer) {
|
||||
return;
|
||||
}
|
||||
onCollapseRest = () => {
|
||||
if (!this.props.expand && this.state.selectedAction) {
|
||||
this.setActiveAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
this.setState(
|
||||
{
|
||||
selectedAction: type,
|
||||
},
|
||||
() => {
|
||||
const translateY = actionContainer.offsetHeight;
|
||||
onActionButtonClick = (type: string | null) => () => {
|
||||
this.setActiveAction(type === this.state.selectedAction ? null : type);
|
||||
};
|
||||
|
||||
this.setState({ translateY });
|
||||
},
|
||||
);
|
||||
};
|
||||
onResetSubmit = (resetClientSecret: boolean) => async () => {
|
||||
const { onResetSubmit, application } = this.props;
|
||||
|
||||
onTileToggle = () => {
|
||||
const { onTileClick, application } = this.props;
|
||||
this.setState({
|
||||
isActionPerforming: true,
|
||||
});
|
||||
|
||||
onTileClick(application.clientId);
|
||||
};
|
||||
await onResetSubmit(application.clientId, resetClientSecret);
|
||||
|
||||
onCollapseRest = () => {
|
||||
if (!this.props.expand && this.state.selectedAction) {
|
||||
this.setActiveAction(null);
|
||||
}
|
||||
};
|
||||
this.setState({
|
||||
isActionPerforming: false,
|
||||
});
|
||||
this.setActiveAction(null);
|
||||
};
|
||||
|
||||
onActionButtonClick = (type: string | null) => () => {
|
||||
this.setActiveAction(type === this.state.selectedAction ? null : type);
|
||||
};
|
||||
onSubmitDelete = () => {
|
||||
const { onDeleteSubmit, application } = this.props;
|
||||
|
||||
onResetSubmit = (resetClientSecret: boolean) => async () => {
|
||||
const { onResetSubmit, application } = this.props;
|
||||
this.setState({
|
||||
isActionPerforming: true,
|
||||
});
|
||||
|
||||
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);
|
||||
};
|
||||
onDeleteSubmit(application.clientId);
|
||||
};
|
||||
}
|
||||
|
@@ -10,103 +10,94 @@ 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;
|
||||
applications: OauthAppResponse[];
|
||||
deleteApp: (clientId: string) => Promise<any>;
|
||||
resetApp: (clientId: string, resetClientSecret: boolean) => Promise<any>;
|
||||
resetClientId: () => void;
|
||||
clientId: string | null;
|
||||
};
|
||||
|
||||
type State = {
|
||||
expandedApp: string | null;
|
||||
expandedApp: string | null;
|
||||
};
|
||||
|
||||
export default class ApplicationsList extends React.Component<Props, State> {
|
||||
state = {
|
||||
expandedApp: null,
|
||||
};
|
||||
state = {
|
||||
expandedApp: null,
|
||||
};
|
||||
|
||||
appsRefs: { [key: string]: HTMLDivElement | null } = {};
|
||||
appsRefs: { [key: string]: HTMLDivElement | null } = {};
|
||||
|
||||
componentDidMount() {
|
||||
this.checkForActiveApp();
|
||||
}
|
||||
componentDidMount() {
|
||||
this.checkForActiveApp();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.checkForActiveApp();
|
||||
}
|
||||
componentDidUpdate() {
|
||||
this.checkForActiveApp();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { applications, resetApp, deleteApp } = this.props;
|
||||
const { expandedApp } = this.state;
|
||||
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}
|
||||
/>
|
||||
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>
|
||||
))}
|
||||
</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);
|
||||
}
|
||||
});
|
||||
};
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@@ -3,48 +3,44 @@ import { OauthAppResponse } from 'app/services/api/oauth';
|
||||
import { Action } from './actions';
|
||||
|
||||
export interface Apps {
|
||||
available: Array<OauthAppResponse>;
|
||||
available: Array<OauthAppResponse>;
|
||||
}
|
||||
|
||||
const defaults: Apps = {
|
||||
available: [],
|
||||
available: [],
|
||||
};
|
||||
|
||||
export default function apps(state: Apps = defaults, action: Action): Apps {
|
||||
switch (action.type) {
|
||||
case 'apps:setAvailable':
|
||||
return {
|
||||
...state,
|
||||
available: action.payload,
|
||||
};
|
||||
switch (action.type) {
|
||||
case 'apps:setAvailable':
|
||||
return {
|
||||
...state,
|
||||
available: action.payload,
|
||||
};
|
||||
|
||||
case 'apps:addApp': {
|
||||
const { payload } = action;
|
||||
const available = [...state.available];
|
||||
let index = available.findIndex(
|
||||
(app) => app.clientId === payload.clientId,
|
||||
);
|
||||
case 'apps:addApp': {
|
||||
const { payload } = action;
|
||||
const available = [...state.available];
|
||||
let index = available.findIndex((app) => app.clientId === payload.clientId);
|
||||
|
||||
if (index === -1) {
|
||||
index = available.length;
|
||||
}
|
||||
if (index === -1) {
|
||||
index = available.length;
|
||||
}
|
||||
|
||||
available[index] = action.payload;
|
||||
available[index] = action.payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
available,
|
||||
};
|
||||
return {
|
||||
...state,
|
||||
available,
|
||||
};
|
||||
}
|
||||
|
||||
case 'apps:deleteApp':
|
||||
return {
|
||||
...state,
|
||||
available: state.available.filter((app) => app.clientId !== action.payload),
|
||||
};
|
||||
}
|
||||
|
||||
case 'apps:deleteApp':
|
||||
return {
|
||||
...state,
|
||||
available: state.available.filter(
|
||||
(app) => app.clientId !== action.payload,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return state;
|
||||
return state;
|
||||
}
|
||||
|
Reference in New Issue
Block a user