-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Manage external apps in editor (#1332)
- Loading branch information
Showing
12 changed files
with
701 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
services/editor/src/pages/settings/components/ExternalAppsPage/CreateExternalAppSecret.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import React from 'react'; | ||
import { buttons } from '../../../../store/ducks'; | ||
import './CreateExternalAppSecret.css'; | ||
|
||
const Component = ({ appId, appSecret }) => ( | ||
<div> | ||
<div className="note"> | ||
Write out the application id and the secret before closing this window. | ||
</div> | ||
<div className="field-wrapper"> | ||
<label className="field-label">App Id:</label> | ||
<div>{appId}</div> | ||
</div> | ||
<div className="field-wrapper"> | ||
<label className="field-label">Secret:</label> | ||
<div className="long-text">{appSecret}</div> | ||
</div> | ||
</div> | ||
); | ||
|
||
export default (appId, appSecret) => ({ | ||
title: 'Important!', | ||
component: () => <Component appId={appId} appSecret={appSecret} />, | ||
buttons: [buttons.OK], | ||
}); |
34 changes: 34 additions & 0 deletions
34
services/editor/src/pages/settings/components/ExternalAppsPage/CreateExternalAppSecret.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
@import "../../../../styles/core/core.less"; | ||
|
||
@small-font: 16px; | ||
|
||
.label-text() { | ||
all: unset; | ||
font-size: @small-font; | ||
margin: 0px 5px 0 0; | ||
color: gray; | ||
display: flex; | ||
white-space: nowrap; | ||
} | ||
|
||
.field-wrapper { | ||
display: flex; | ||
flex-grow: 1; | ||
flex-direction: row; | ||
margin-top: 5px; | ||
|
||
.field-label { | ||
.label-text(); | ||
align-items: center; | ||
padding-bottom: 1px; | ||
} | ||
|
||
.long-text { | ||
overflow-wrap: anywhere; | ||
} | ||
} | ||
|
||
.note { | ||
overflow-wrap: break-word; | ||
font-style: italic; | ||
} |
117 changes: 117 additions & 0 deletions
117
services/editor/src/pages/settings/components/ExternalAppsPage/EditExternalAppPage.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import React, { useState, useMemo, useContext, useCallback } from 'react'; | ||
import { WithContext as ReactTags } from 'react-tag-input'; | ||
import { tweekManagementClient, useErrorNotifier } from '../../../../utils'; | ||
import { SaveButton } from '../../../../components/common'; | ||
import { ReduxContext } from '../../../../store'; | ||
import { showSuccess, showCustomAlert } from '../../../../store/ducks'; | ||
import createAlert from './CreateExternalAppSecret'; | ||
import './EditExternalAppPage.css'; | ||
|
||
export default ({ location, history }) => { | ||
const { dispatch } = useContext(ReduxContext); | ||
const initialExternalAppData = (location.state && location.state.externalApp) || {}; | ||
const id = initialExternalAppData.id; | ||
const isEditPage = Boolean(id); | ||
|
||
const [name, setName] = useState(initialExternalAppData.name || ''); | ||
const [permissions, setPermissions] = useState(initialExternalAppData.permissions || []); | ||
const [saveError, setSaveError] = useState(null); | ||
const [isSaving, setIsSaving] = useState(false); | ||
|
||
const isValid = useMemo(() => validateInput({ name, permissions }), [name, permissions]); | ||
const hasChanges = useMemo(() => checkForChanges({ initialExternalAppData, name, permissions }), [ | ||
name, | ||
permissions, | ||
]); | ||
|
||
useErrorNotifier(saveError, 'Failed to save external app'); | ||
|
||
return ( | ||
<div className="edit-external-app-page"> | ||
<h3>{isEditPage ? 'Edit External App' : 'Add New External App'}</h3> | ||
|
||
<TextField label="Name:" value={name} setter={setName} /> | ||
<PermissionsPicker permissions={permissions} setPermissions={setPermissions} /> | ||
|
||
<SaveButton | ||
{...{ isSaving, isValid, hasChanges }} | ||
onClick={useSaveExternalAppCallback({ | ||
id, | ||
name, | ||
permissions, | ||
setIsSaving, | ||
history, | ||
setSaveError, | ||
dispatch, | ||
})} | ||
/> | ||
</div> | ||
); | ||
}; | ||
|
||
const TextField = ({ label, value, setter, placeholder }) => ( | ||
<div className="field-input-wrapper"> | ||
<label className="field-label">{label}</label> | ||
<input type="text" onChange={(e) => setter(e.target.value)} {...{ value, placeholder }} /> | ||
</div> | ||
); | ||
|
||
const PermissionsPicker = ({ permissions, setPermissions }) => ( | ||
<div className="permissions-picker"> | ||
<label className="field-label">Permissions:</label> | ||
<ReactTags | ||
tags={permissions.map((permission) => ({ id: permission, text: permission }))} | ||
handleDelete={(index) => setPermissions(permissions.filter((_, i) => i !== index))} | ||
handleAddition={(permission) => setPermissions([...permissions, permission.text])} | ||
placeholder="New permission" | ||
autofocus={false} | ||
allowDeleteFromEmptyInput | ||
allowDragDrop={false} | ||
minQueryLength={1} | ||
classNames={{ | ||
tags: 'tags-container', | ||
tagInput: 'tag-input', | ||
tag: 'tag', | ||
remove: 'tag-delete-button', | ||
suggestions: 'tags-suggestion', | ||
}} | ||
/> | ||
</div> | ||
); | ||
|
||
const useSaveExternalAppCallback = ({ | ||
id, | ||
name, | ||
permissions, | ||
setIsSaving, | ||
history, | ||
setSaveError, | ||
dispatch, | ||
}) => | ||
useCallback(async () => { | ||
try { | ||
setIsSaving(true); | ||
|
||
if (id) await tweekManagementClient.updateExternalApp(id, { name, permissions }); | ||
else { | ||
const { appId, appSecret } = await tweekManagementClient.createExternalApp({ | ||
name, | ||
permissions, | ||
}); | ||
await dispatch(showCustomAlert(createAlert(appId, appSecret))); | ||
} | ||
|
||
setIsSaving(false); | ||
dispatch(showSuccess({ title: 'External App Saved' })); | ||
history.goBack(); | ||
} catch (err) { | ||
setIsSaving(false); | ||
setSaveError(err); | ||
} | ||
}, [id, name, permissions]); | ||
|
||
const validateInput = ({ name, permissions }) => | ||
Boolean(name && permissions && Array.isArray(permissions)); | ||
|
||
const checkForChanges = ({ initialExternalAppData, name, permissions }) => | ||
name !== initialExternalAppData.name || permissions !== initialExternalAppData.permissions; |
68 changes: 68 additions & 0 deletions
68
services/editor/src/pages/settings/components/ExternalAppsPage/EditExternalAppPage.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
@import "../../../../styles/core/core.less"; | ||
@import "../../../../styles/core/comboBox.less"; | ||
@import (reference) "../../../../styles/core/tags.less"; | ||
|
||
@text-padding: 10px; | ||
@panel-padding: 35px; | ||
@small-font: 16px; | ||
@smaller-font: 15px; | ||
|
||
@button-width: 100px; | ||
@button-height: 25px; | ||
|
||
.edit-external-app-page { | ||
.panel; | ||
padding: @panel-padding; | ||
border-width: 0; | ||
box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.2); | ||
margin-top: 18px * 2 + 36px; | ||
margin-bottom: auto; | ||
margin-left: auto; | ||
margin-right: auto; | ||
display: flex; | ||
flex-direction: column; | ||
flex-grow: 0.5; | ||
|
||
h3 { | ||
font-size: 22px; | ||
padding-left: @text-padding; | ||
margin: 0px 0px 58px 0px; | ||
font-weight: 400; | ||
} | ||
|
||
.field-input-wrapper { | ||
display: flex; | ||
flex-direction: row; | ||
flex-grow: 1; | ||
align-content: space-between; | ||
padding-left: @text-padding; | ||
padding-right: @button-width + @panel-padding; | ||
margin-bottom: 41px; | ||
|
||
> input { | ||
.metro-textbox; | ||
font-size: @small-font; | ||
width: 550px; | ||
} | ||
} | ||
|
||
.field-label { | ||
.label-text(); | ||
align-items: center; | ||
padding-bottom: 1px; | ||
} | ||
|
||
.permissions-picker { | ||
.field-input-wrapper; | ||
.metro-tags; | ||
} | ||
} | ||
|
||
.label-text() { | ||
all: unset; | ||
font-size: @small-font; | ||
margin: 0px 5px 0 0; | ||
color: gray; | ||
display: flex; | ||
white-space: nowrap; | ||
} |
Oops, something went wrong.