mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 22:30:15 +00:00
Cleanup before major team features (#72)
* Started on UI makeover for teams * Some small tweaks and fixes * Adjusted settings
This commit is contained in:
parent
c98729e9e3
commit
d58edf1d25
65
app/__mocks__/node-forge.js
Normal file
65
app/__mocks__/node-forge.js
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This is a stupid little mock that basically disabled encryption.
|
||||
* The reason it is needed is because the Forge module loader doesn't
|
||||
* play along with Jest.
|
||||
*/
|
||||
|
||||
module.exports = function (options) {
|
||||
const forge = require('../../node_modules/node-forge/js/forge')(options);
|
||||
|
||||
forge.util = {
|
||||
hexToBytes: forge.hexToBytes,
|
||||
bytesToHex: forge.bytesToHex,
|
||||
createBuffer (text) {
|
||||
return new Buffer(text);
|
||||
}
|
||||
};
|
||||
|
||||
forge.pkcs5 = {
|
||||
pbkdf2 () {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
forge.random = {
|
||||
getBytesSync(n) {
|
||||
let s = '';
|
||||
for (let i = 0; i < n; i++) {
|
||||
s += 'a';
|
||||
}
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
forge.cipher = {
|
||||
createCipher(alg, key) {
|
||||
return {
|
||||
start(config) {
|
||||
this._config = config;
|
||||
},
|
||||
update(buffer) {
|
||||
this._data = buffer;
|
||||
},
|
||||
finish() {
|
||||
this.mode = {tag: 'tag'};
|
||||
this.output = this._data;
|
||||
}
|
||||
};
|
||||
},
|
||||
createDecipher(alg, key) {
|
||||
return {
|
||||
start (config) {
|
||||
this._config = config;
|
||||
},
|
||||
update (buffer) {
|
||||
this.output = buffer;
|
||||
},
|
||||
finish () {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return forge;
|
||||
};
|
@ -9,12 +9,10 @@ export async function init (accountId) {
|
||||
return;
|
||||
}
|
||||
|
||||
process.nextTick(() => {
|
||||
google.init(accountId, getAppPlatform(), getAppVersion());
|
||||
segment.init();
|
||||
await segment.init();
|
||||
await google.init(accountId, getAppPlatform(), getAppVersion());
|
||||
|
||||
initialized = true;
|
||||
});
|
||||
initialized = true;
|
||||
|
||||
ipcRenderer.on('analytics-track-event', (_, args) => {
|
||||
trackEvent(...args);
|
||||
|
@ -121,7 +121,7 @@ export const CONTENT_TYPE_OTHER = '';
|
||||
export const contentTypesMap = {
|
||||
[CONTENT_TYPE_JSON]: 'JSON',
|
||||
[CONTENT_TYPE_XML]: 'XML',
|
||||
[CONTENT_TYPE_FORM_DATA]: 'Form Data',
|
||||
[CONTENT_TYPE_FORM_DATA]: 'Multipart Form',
|
||||
[CONTENT_TYPE_FORM_URLENCODED]: 'Url Encoded',
|
||||
[CONTENT_TYPE_FILE]: 'Binary File',
|
||||
[CONTENT_TYPE_OTHER]: 'Other'
|
||||
|
@ -107,6 +107,8 @@ export function updateMimeType (request, mimeType, doCreate = false) {
|
||||
body = newBodyForm(request.body.params || []);
|
||||
} else if (mimeType === CONTENT_TYPE_FILE) {
|
||||
body = newBodyFile('');
|
||||
} else if (typeof mimeType !== 'string') {
|
||||
body = newBodyRaw('');
|
||||
} else {
|
||||
body = newBodyRaw(request.body.text || '', mimeType);
|
||||
}
|
||||
|
@ -22,11 +22,10 @@
|
||||
"mime-types": "^2.1.12",
|
||||
"mkdirp": "^0.5.1",
|
||||
"nedb": "^1.8.0",
|
||||
"node-forge": "^0.6.43",
|
||||
"node-forge": "^0.6.46",
|
||||
"nunjucks": "^3.0.0",
|
||||
"raven": "^0.12.1",
|
||||
"request": "^2.71.0",
|
||||
"sjcl": "^1.0.6",
|
||||
"srp": "git@github.com:getinsomnia/srp-js.git#6ebd8c3acfbcf69645e4c6e6f8f6b55138ff22c3",
|
||||
"tough-cookie": "^2.3.1",
|
||||
"traverse": "^0.6.6",
|
||||
|
@ -117,16 +117,5 @@
|
||||
// SOME HELPERS
|
||||
document.body.setAttribute('data-platform', process.platform);
|
||||
</script>
|
||||
|
||||
<!-- Other Scripts -->
|
||||
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const key = process.env.INSOMNIA_ENV === 'development' ?
|
||||
'pk_test_MbOhGu5jCPvr7Jt4VC6oySdH' :
|
||||
'pk_live_lntbVSXY3v1RAytACIQJ5BBH';
|
||||
Stripe.setPublishableKey(key);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
30
app/sync/__tests__/crypt.test.js
Normal file
30
app/sync/__tests__/crypt.test.js
Normal file
@ -0,0 +1,30 @@
|
||||
import * as crypt from '../crypt';
|
||||
|
||||
describe('deriveKey()', () => {
|
||||
it('derives a key properly', async () => {
|
||||
const result = await crypt.deriveKey('Password', 'email', 'salt');
|
||||
|
||||
const expected = '';
|
||||
|
||||
expect(result).toBe(expected);
|
||||
})
|
||||
});
|
||||
|
||||
describe('encryptRSA', () => {
|
||||
it('encrypts and decrypts', () => {
|
||||
const resultEncrypted = crypt.encryptAES('rawkey', 'Hello World!', 'additional data');
|
||||
const resultDecrypted = crypt.decryptAES('rawkey', resultEncrypted);
|
||||
|
||||
const expectedEncrypted = {
|
||||
ad: '6164646974696f6e616c2064617461',
|
||||
d: '48656c6c6f253230576f726c6421',
|
||||
iv: '616161616161616161616161',
|
||||
t: '746167'
|
||||
};
|
||||
|
||||
const expectedDecrypted = 'Hello World!';
|
||||
|
||||
expect(resultEncrypted).toEqual(expectedEncrypted);
|
||||
expect(resultDecrypted).toEqual(expectedDecrypted);
|
||||
})
|
||||
});
|
@ -1,7 +1,9 @@
|
||||
import HKDF from 'hkdf';
|
||||
import sjcl from 'sjcl';
|
||||
import srp from 'srp';
|
||||
import forge from 'node-forge';
|
||||
import initForge from 'node-forge';
|
||||
|
||||
// Initialize Forge
|
||||
const forge = initForge();
|
||||
|
||||
const DEFAULT_BYTE_LENGTH = 32;
|
||||
const DEFAULT_PBKDF2_ITERATIONS = 1E5; // 100,000
|
||||
@ -182,7 +184,7 @@ export async function generateAES256Key () {
|
||||
);
|
||||
return subtle.exportKey('jwk', key);
|
||||
} else {
|
||||
console.log('-- Using Falback Forge AES Key Generation --');
|
||||
console.log('-- Using Fallback Forge AES Key Generation --');
|
||||
const key = forge.util.bytesToHex(forge.random.getBytesSync(32));
|
||||
return {
|
||||
kty: 'oct',
|
||||
@ -200,7 +202,7 @@ export async function generateAES256Key () {
|
||||
* @returns Object
|
||||
*/
|
||||
export async function generateKeyPairJWK () {
|
||||
// NOTE: Safari has crypto.webkitSubtle, but does not support RSA-OAEP-SHA256
|
||||
// NOTE: Safari has crypto.webkitSubtle, but it does not support RSA-OAEP-SHA256
|
||||
const subtle = window.crypto && window.crypto.subtle;
|
||||
|
||||
if (subtle) {
|
||||
@ -298,9 +300,6 @@ function _b64UrlToHex (s) {
|
||||
return forge.util.bytesToHex(atob(b64));
|
||||
}
|
||||
|
||||
window.b64urltohex = _b64UrlToHex;
|
||||
window.forge = forge;
|
||||
|
||||
/**
|
||||
* Derive key from password
|
||||
*
|
||||
@ -329,26 +328,15 @@ async function _pbkdf2Passphrase (passphrase, salt) {
|
||||
const derivedKeyRaw = await window.crypto.subtle.deriveBits(algo, k, DEFAULT_BYTE_LENGTH * 8);
|
||||
return new Buffer(derivedKeyRaw).toString('hex');
|
||||
} else {
|
||||
console.log('-- Using SJCL PBKDF2 --');
|
||||
|
||||
const derivedKeyRaw = sjcl.misc.pbkdf2(
|
||||
console.log('-- Using Forge PBKDF2 --');
|
||||
const derivedKeyRaw = forge.pkcs5.pbkdf2(
|
||||
passphrase,
|
||||
sjcl.codec.hex.toBits(salt),
|
||||
forge.util.hexToBytes(salt),
|
||||
DEFAULT_PBKDF2_ITERATIONS,
|
||||
DEFAULT_BYTE_LENGTH * 8,
|
||||
sjcl.hash.sha1
|
||||
DEFAULT_BYTE_LENGTH,
|
||||
forge.md.sha256.create()
|
||||
);
|
||||
|
||||
return sjcl.codec.hex.fromBits(derivedKeyRaw);
|
||||
|
||||
// NOTE: SJCL (above) is about 10x faster than Forge
|
||||
// const derivedKeyRaw = forge.pkcs5.pbkdf2(
|
||||
// passphrase,
|
||||
// forge.util.hexToBytes(salt),
|
||||
// DEFAULT_PBKDF2_ITERATIONS,
|
||||
// DEFAULT_BYTE_LENGTH,
|
||||
// forge.md.sha256.create()
|
||||
// );
|
||||
// const derivedKey = forge.util.bytesToHex(derivedKeyRaw);
|
||||
return forge.util.bytesToHex(derivedKeyRaw);
|
||||
}
|
||||
}
|
||||
|
@ -233,7 +233,10 @@ export async function logout () {
|
||||
trackEvent('Session', 'Logout');
|
||||
}
|
||||
|
||||
/** Cancel the user's subscription Account */
|
||||
export async function listTeams () {
|
||||
return util.get('/api/teams');
|
||||
}
|
||||
|
||||
export async function cancelAccount () {
|
||||
await util.del('/api/billing/subscriptions');
|
||||
trackEvent('Session', 'Cancel Account');
|
||||
|
@ -17,6 +17,7 @@ class RequestUrlBar extends Component {
|
||||
|
||||
_handleFormSubmit = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.handleSend();
|
||||
};
|
||||
|
||||
@ -117,8 +118,8 @@ class RequestUrlBar extends Component {
|
||||
clearTimeout(this._sendInterval);
|
||||
if (this.state.currentInterval) {
|
||||
this.setState({currentInterval: null});
|
||||
trackEvent('Request', 'Stop Send Interval');
|
||||
}
|
||||
trackEvent('Request', 'Stop Send Interval');
|
||||
};
|
||||
|
||||
_handleStopTimeout = () => {
|
||||
@ -129,6 +130,20 @@ class RequestUrlBar extends Component {
|
||||
trackEvent('Request', 'Stop Send Timeout');
|
||||
};
|
||||
|
||||
_handleClickSend = e => {
|
||||
const metaPressed = isMac() ? e.metaKey : e.ctrlKey;
|
||||
|
||||
// If we're pressing a meta key, let the dropdown open
|
||||
if (metaPressed) {
|
||||
e.preventDefault(); // Don't submit the form
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're not pressing a meta key, cancel dropdown and send the request
|
||||
e.stopPropagation(); // Don't trigger the dropdown
|
||||
this._handleFormSubmit(e);
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
document.body.addEventListener('keydown', this._handleKeyDown);
|
||||
document.body.addEventListener('keyup', this._handleKeyUp);
|
||||
@ -140,7 +155,7 @@ class RequestUrlBar extends Component {
|
||||
}
|
||||
|
||||
renderSendButton () {
|
||||
const {showAdvanced, currentInterval, currentTimeout} = this.state;
|
||||
const {currentInterval, currentTimeout} = this.state;
|
||||
|
||||
let cancelButton = null;
|
||||
if (currentInterval) {
|
||||
@ -164,22 +179,15 @@ class RequestUrlBar extends Component {
|
||||
}
|
||||
|
||||
let sendButton;
|
||||
if (!cancelButton && !showAdvanced) {
|
||||
sendButton = (
|
||||
<button key="send" type="submit" className="urlbar__send-btn">
|
||||
Send
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
let dropdown = null;
|
||||
if (!cancelButton) {
|
||||
dropdown = (
|
||||
sendButton = (
|
||||
<Dropdown key="dropdown" className="tall" right={true}>
|
||||
<DropdownButton className={classnames('urlbar__send-btn', {hide: !showAdvanced})}>
|
||||
<i className="fa fa-caret-down"></i>
|
||||
<DropdownButton className="urlbar__send-btn"
|
||||
onClick={this._handleClickSend}
|
||||
type="submit">
|
||||
Send
|
||||
</DropdownButton>
|
||||
<DropdownDivider name="Basic"/>
|
||||
<DropdownDivider>Basic</DropdownDivider>
|
||||
<DropdownItem type="submit">
|
||||
<i className="fa fa-arrow-circle-o-right"/> Send Now
|
||||
<DropdownHint char="Enter"/>
|
||||
@ -187,7 +195,7 @@ class RequestUrlBar extends Component {
|
||||
<DropdownItem onClick={this._handleGenerateCode}>
|
||||
<i className="fa fa-code"/> Generate Client Code
|
||||
</DropdownItem>
|
||||
<DropdownDivider name="Advanced"/>
|
||||
<DropdownDivider>Advanced</DropdownDivider>
|
||||
<DropdownItem onClick={this._handleSendAfterDelay}>
|
||||
<i className="fa fa-clock-o"/> Send After Delay
|
||||
</DropdownItem>
|
||||
@ -201,7 +209,6 @@ class RequestUrlBar extends Component {
|
||||
return [
|
||||
cancelButton,
|
||||
sendButton,
|
||||
dropdown,
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,6 @@ import CookiesModal from '../components/modals/CookiesModal';
|
||||
import EnvironmentEditModal from '../components/modals/EnvironmentEditModal';
|
||||
import GenerateCodeModal from '../components/modals/GenerateCodeModal';
|
||||
import LoginModal from '../components/modals/LoginModal';
|
||||
import PaymentModal from '../components/modals/PaymentModal';
|
||||
import PaymentNotificationModal from '../components/modals/PaymentNotificationModal';
|
||||
import PromptModal from '../components/modals/PromptModal';
|
||||
import RequestCreateModal from '../components/modals/RequestCreateModal';
|
||||
@ -16,10 +15,10 @@ import RequestSwitcherModal from '../components/modals/RequestSwitcherModal';
|
||||
import ResponsePane from './ResponsePane';
|
||||
import SettingsModal from '../components/modals/SettingsModal';
|
||||
import Sidebar from './sidebar/Sidebar';
|
||||
import SignupModal from '../components/modals/SignupModal';
|
||||
import SyncLogsModal from '../components/modals/SyncLogsModal';
|
||||
import WorkspaceEnvironmentsEditModal from '../components/modals/WorkspaceEnvironmentsEditModal';
|
||||
import WorkspaceSettingsModal from '../components/modals/WorkspaceSettingsModal';
|
||||
import WorkspaceShareSettingsModal from '../components/modals/WorkspaceShareSettingsModal';
|
||||
import * as models from '../../models/index';
|
||||
import {updateMimeType} from '../../models/request';
|
||||
import {trackEvent} from '../../analytics/index';
|
||||
@ -276,14 +275,15 @@ class Wrapper extends Component {
|
||||
<SyncLogsModal ref={registerModal}/>
|
||||
<LoginModal ref={registerModal}/>
|
||||
<PromptModal ref={registerModal}/>
|
||||
<SignupModal ref={registerModal}/>
|
||||
<PaymentModal ref={registerModal}/>
|
||||
<RequestCreateModal ref={registerModal}/>
|
||||
<PaymentNotificationModal ref={registerModal}/>
|
||||
<WorkspaceSettingsModal
|
||||
ref={registerModal}
|
||||
workspace={activeWorkspace}
|
||||
handleRemoveWorkspace={this._handleRemoveActiveWorkspace}/>
|
||||
<WorkspaceShareSettingsModal
|
||||
ref={registerModal}
|
||||
workspace={activeWorkspace}/>
|
||||
<EnvironmentEditModal
|
||||
ref={registerModal}
|
||||
onChange={models.requestGroup.update}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import * as misc from '../../../common/misc';
|
||||
|
||||
class Editable extends Component {
|
||||
state = {editing: false};
|
||||
@ -25,7 +24,7 @@ class Editable extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
_handleEditEnd = async () => {
|
||||
_handleEditEnd = () => {
|
||||
const value = this._input.value.trim();
|
||||
|
||||
if (!value) {
|
||||
@ -37,8 +36,7 @@ class Editable extends Component {
|
||||
|
||||
// This timeout prevents the UI from showing the old value after submit.
|
||||
// It should give the UI enough time to redraw the new value.
|
||||
await misc.delay(100);
|
||||
this.setState({editing: false});
|
||||
setTimeout(async () => this.setState({editing: false}), 100);
|
||||
};
|
||||
|
||||
_handleEditKeyDown = e => {
|
||||
|
@ -102,7 +102,7 @@ class Dropdown extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {right, outline, wide, className, style} = this.props;
|
||||
const {right, outline, wide, className, style, children} = this.props;
|
||||
const {dropUp, open} = this.state;
|
||||
|
||||
const classes = classnames(
|
||||
@ -117,7 +117,9 @@ class Dropdown extends Component {
|
||||
|
||||
const dropdownButtons = [];
|
||||
const dropdownItems = [];
|
||||
for (const child of this._getFlattenedChildren(this.props.children)) {
|
||||
|
||||
const allChildren = this._getFlattenedChildren(Array.isArray(children) ? children : [children]);
|
||||
for (const child of allChildren) {
|
||||
if (child.type === DropdownButton) {
|
||||
dropdownButtons.push(child);
|
||||
} else if (child.type === DropdownItem) {
|
||||
@ -127,13 +129,11 @@ class Dropdown extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
let children = [];
|
||||
let finalChildren = [];
|
||||
if (dropdownButtons.length !== 1) {
|
||||
console.error(`Dropdown needs exactly one DropdownButton! Got ${dropdownButtons.length}`, this.props);
|
||||
} else if (dropdownItems.length === 0) {
|
||||
children = dropdownButtons;
|
||||
} else {
|
||||
children = [
|
||||
finalChildren = [
|
||||
dropdownButtons[0],
|
||||
<ul key="items" ref={this._addDropdownListRef}>
|
||||
{dropdownItems}
|
||||
@ -146,7 +146,7 @@ class Dropdown extends Component {
|
||||
className={classes}
|
||||
onClick={this._handleClick}
|
||||
onMouseDown={this._handleMouseDown}>
|
||||
{children}
|
||||
{finalChildren}
|
||||
<div className="dropdown__backdrop"></div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,23 +1,21 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const DropdownDivider = ({name}) => {
|
||||
const DropdownDivider = ({children}) => {
|
||||
const classes = classnames(
|
||||
'dropdown__divider',
|
||||
{'dropdown__divider--no-name': !name}
|
||||
{'dropdown__divider--no-name': !children}
|
||||
);
|
||||
|
||||
return (
|
||||
<li className={classes}>
|
||||
<span className="dropdown__divider__label">
|
||||
{name}
|
||||
{children}
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
};
|
||||
|
||||
DropdownDivider.propTypes = {
|
||||
name: PropTypes.any
|
||||
};
|
||||
DropdownDivider.propTypes = {};
|
||||
|
||||
export default DropdownDivider;
|
||||
|
@ -34,14 +34,14 @@ class ContentTypeDropdown extends Component {
|
||||
<DropdownButton className={className}>
|
||||
{children}
|
||||
</DropdownButton>
|
||||
<DropdownDivider name={<span><i className="fa fa-bars"></i> Form Data</span>}/>
|
||||
<DropdownDivider><span><i className="fa fa-bars"></i> Form Data</span></DropdownDivider>
|
||||
{this._renderDropdownItem(constants.CONTENT_TYPE_FORM_DATA)}
|
||||
{this._renderDropdownItem(constants.CONTENT_TYPE_FORM_URLENCODED)}
|
||||
<DropdownDivider name={<span><i className="fa fa-code"></i> Raw Text</span>}/>
|
||||
<DropdownDivider><span><i className="fa fa-code"></i> Raw Text</span></DropdownDivider>
|
||||
{this._renderDropdownItem(constants.CONTENT_TYPE_JSON)}
|
||||
{this._renderDropdownItem(constants.CONTENT_TYPE_XML)}
|
||||
{this._renderDropdownItem(constants.CONTENT_TYPE_OTHER)}
|
||||
<DropdownDivider name={<span><i className="fa fa-ellipsis-h"></i> Other</span>}/>
|
||||
<DropdownDivider><span><i className="fa fa-ellipsis-h"></i> Other</span></DropdownDivider>
|
||||
{this._renderDropdownItem(constants.CONTENT_TYPE_FILE)}
|
||||
{this._renderDropdownItem(EMPTY_MIME_TYPE, 'No Body')}
|
||||
</Dropdown>
|
||||
|
@ -2,11 +2,10 @@ import React, {Component, PropTypes} from 'react';
|
||||
import {ipcRenderer} from 'electron';
|
||||
import classnames from 'classnames';
|
||||
import EnvironmentsModal from '../modals/WorkspaceEnvironmentsEditModal';
|
||||
import {Dropdown, DropdownDivider, DropdownButton, DropdownItem} from '../base/dropdown';
|
||||
import {Dropdown, DropdownDivider, DropdownButton, DropdownItem, DropdownHint} from '../base/dropdown';
|
||||
import {showModal} from '../modals/index';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
|
||||
|
||||
const EnvironmentsDropdown = ({
|
||||
className,
|
||||
workspace,
|
||||
@ -17,7 +16,9 @@ const EnvironmentsDropdown = ({
|
||||
}) => {
|
||||
// NOTE: Base environment might not exist if the users hasn't managed environments yet.
|
||||
const baseEnvironment = environments.find(e => e.parentId === workspace._id);
|
||||
const subEnvironments = environments.filter(e => e.parentId === (baseEnvironment && baseEnvironment._id));
|
||||
const subEnvironments = environments.filter(
|
||||
e => e.parentId === (baseEnvironment && baseEnvironment._id)
|
||||
);
|
||||
|
||||
let description;
|
||||
if (!activeEnvironment || activeEnvironment === baseEnvironment) {
|
||||
@ -35,7 +36,7 @@ const EnvironmentsDropdown = ({
|
||||
<i className="fa fa-caret-down"></i>
|
||||
</div>
|
||||
</DropdownButton>
|
||||
<DropdownDivider name="Switch Environment"/>
|
||||
<DropdownDivider>Switch Environment</DropdownDivider>
|
||||
{subEnvironments.map(environment => (
|
||||
<DropdownItem key={environment._id}
|
||||
onClick={e => {
|
||||
@ -51,9 +52,10 @@ const EnvironmentsDropdown = ({
|
||||
}}>
|
||||
<i className="fa fa-empty"></i> No Environment
|
||||
</DropdownItem>
|
||||
<DropdownDivider name="General"/>
|
||||
<DropdownDivider>General</DropdownDivider>
|
||||
<DropdownItem onClick={e => showModal(EnvironmentsModal, workspace)}>
|
||||
<i className="fa fa-wrench"></i> Manage Environments
|
||||
<DropdownHint char="E"/>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
)
|
||||
|
@ -16,14 +16,14 @@ class PreviewModeDropdown extends PureComponent {
|
||||
<DropdownButton className="tall">
|
||||
<i className="fa fa-caret-down"></i>
|
||||
</DropdownButton>
|
||||
<DropdownDivider name="Preview Mode"/>
|
||||
<DropdownDivider>Preview Mode</DropdownDivider>
|
||||
{PREVIEW_MODES.map(mode => (
|
||||
<DropdownItem key={mode} onClick={this._handleClick} value={mode}>
|
||||
{previewMode === mode ? <i className="fa fa-check"/> : <i className="fa fa-empty"/>}
|
||||
{getPreviewModeName(mode)}
|
||||
</DropdownItem>
|
||||
))}
|
||||
<DropdownDivider name="Actions"/>
|
||||
<DropdownDivider>Actions</DropdownDivider>
|
||||
<DropdownItem onClick={download}>
|
||||
<i className="fa fa-save"></i>
|
||||
Save to File
|
||||
|
@ -5,6 +5,8 @@ import PromptModal from '../modals/PromptModal';
|
||||
import * as models from '../../../models';
|
||||
import {showModal} from '../modals/index';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import AlertModal from '../modals/AlertModal';
|
||||
import {MOD_SYM} from '../../../common/constants';
|
||||
|
||||
|
||||
class RequestActionsDropdown extends Component {
|
||||
@ -19,6 +21,19 @@ class RequestActionsDropdown extends Component {
|
||||
trackEvent('Request', 'Generate Code', 'Request Action');
|
||||
};
|
||||
|
||||
_handleAdvancedSend = () => {
|
||||
trackEvent('Request', 'Advanced Send Hint', 'Request Action');
|
||||
showModal(AlertModal, {
|
||||
title: 'Advanced Sending',
|
||||
message: (
|
||||
<div>
|
||||
For advanced sending options, hold <code>{MOD_SYM}</code> while
|
||||
clicking the send button next to the Url.
|
||||
</div>
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
_handlePromptUpdateName = async () => {
|
||||
const {request} = this.props;
|
||||
|
||||
@ -57,6 +72,9 @@ class RequestActionsDropdown extends Component {
|
||||
<DropdownItem onClick={this._handleGenerateCode}>
|
||||
<i className="fa fa-code"></i> Generate Code
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={this._handleAdvancedSend}>
|
||||
<i className="fa fa-refresh"></i> Advanced Sending
|
||||
</DropdownItem>
|
||||
<DropdownItem buttonClass={PromptButton} onClick={this._handleRemove} addIcon={true}>
|
||||
<i className="fa fa-trash-o"></i> Delete
|
||||
</DropdownItem>
|
||||
|
@ -82,14 +82,14 @@ class ResponseHistoryDropdown extends Component {
|
||||
<i className="fa fa-history"/> :
|
||||
<i className="fa fa-thumb-tack"/>}
|
||||
</DropdownButton>
|
||||
<DropdownDivider name="Response History"/>
|
||||
<DropdownDivider>Response History</DropdownDivider>
|
||||
<DropdownItem buttonClass={PromptButton}
|
||||
addIcon={true}
|
||||
onClick={this._handleDeleteResponses}>
|
||||
<i className="fa fa-trash-o"/>
|
||||
Clear History
|
||||
</DropdownItem>
|
||||
<DropdownDivider name="Past Responses"/>
|
||||
<DropdownDivider>Past Responses</DropdownDivider>
|
||||
{responses.map(this.renderDropdownItem)}
|
||||
</Dropdown>
|
||||
)
|
||||
|
@ -6,24 +6,21 @@ import * as syncStorage from '../../../sync/storage';
|
||||
import * as session from '../../../sync/session';
|
||||
import * as sync from '../../../sync';
|
||||
import {trackEvent} from '../../../analytics';
|
||||
import SettingsModal, {TAB_PLUS} from '../modals/SettingsModal';
|
||||
import LoginModal from '../modals/LoginModal';
|
||||
import PromptButton from '../base/PromptButton';
|
||||
|
||||
class SyncDropdown extends Component {
|
||||
state = {
|
||||
loggedIn: null,
|
||||
syncData: null,
|
||||
loading: false,
|
||||
hide: false,
|
||||
};
|
||||
|
||||
_handleHideMenu () {
|
||||
this.setState({hide: true});
|
||||
trackEvent('Sync', 'Hide Menu')
|
||||
}
|
||||
_trackShowMenu = () => trackEvent('Sync', 'Show Menu', 'Authenticated');
|
||||
_handleShowLogs = () => showModal(SyncLogsModal);
|
||||
|
||||
_handleToggleSyncMode = async () => {
|
||||
const {syncData} = this.state;
|
||||
const resourceGroupId = syncData.resourceGroupId;
|
||||
|
||||
async _handleToggleSyncMode (resourceGroupId) {
|
||||
const config = await sync.getOrCreateConfig(resourceGroupId);
|
||||
|
||||
let syncMode = config.syncMode === syncStorage.SYNC_MODE_OFF ?
|
||||
@ -35,13 +32,16 @@ class SyncDropdown extends Component {
|
||||
|
||||
// Trigger a sync right away if we're turning it on
|
||||
if (syncMode === syncStorage.SYNC_MODE_ON) {
|
||||
await this._handleSyncResourceGroupId(resourceGroupId);
|
||||
await this._handleSyncResourceGroupId();
|
||||
}
|
||||
|
||||
trackEvent('Sync', 'Change Mode', syncMode);
|
||||
}
|
||||
};
|
||||
|
||||
_handleSyncResourceGroupId = async () => {
|
||||
const {syncData} = this.state;
|
||||
const resourceGroupId = syncData.resourceGroupId;
|
||||
|
||||
async _handleSyncResourceGroupId (resourceGroupId) {
|
||||
// Set loading state
|
||||
this.setState({loading: true});
|
||||
|
||||
@ -55,7 +55,7 @@ class SyncDropdown extends Component {
|
||||
this.setState({loading: false});
|
||||
|
||||
trackEvent('Sync', 'Manual Sync');
|
||||
}
|
||||
};
|
||||
|
||||
async _reloadData () {
|
||||
const loggedIn = session.isLoggedIn();
|
||||
@ -108,45 +108,11 @@ class SyncDropdown extends Component {
|
||||
|
||||
render () {
|
||||
const {className} = this.props;
|
||||
const {syncData, loading, loggedIn, hide} = this.state;
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
}
|
||||
const {syncData, loading, loggedIn} = this.state;
|
||||
|
||||
// Don't show the sync menu unless we're logged in
|
||||
if (!loggedIn) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Dropdown wide={true} className="wide tall">
|
||||
<DropdownButton className="btn btn--compact wide"
|
||||
onClick={e => trackEvent('Sync', 'Show Menu', 'Guest')}>
|
||||
Sync Settings
|
||||
</DropdownButton>
|
||||
<DropdownDivider name="Insomnia Cloud Sync"/>
|
||||
<DropdownItem onClick={e => {
|
||||
showModal(SettingsModal, TAB_PLUS);
|
||||
trackEvent('Sync', 'Create Account');
|
||||
}}>
|
||||
<i className="fa fa-user"></i>
|
||||
Create Account
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={e => {
|
||||
showModal(LoginModal);
|
||||
trackEvent('Sync', 'Login');
|
||||
}}>
|
||||
<i className="fa fa-empty"></i>
|
||||
Login
|
||||
</DropdownItem>
|
||||
<DropdownDivider/>
|
||||
<DropdownItem buttonClass={PromptButton}
|
||||
addIcon={true}
|
||||
onClick={e => this._handleHideMenu()}>
|
||||
<i className="fa fa-eye-slash"></i>
|
||||
Hide This Menu
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!syncData) {
|
||||
@ -158,25 +124,24 @@ class SyncDropdown extends Component {
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
const {resourceGroupId, syncMode, syncPercent} = syncData;
|
||||
const {syncMode, syncPercent} = syncData;
|
||||
const description = this._getSyncDescription(syncMode, syncPercent);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Dropdown wide={true} className="wide tall">
|
||||
<DropdownButton className="btn btn--compact wide"
|
||||
onClick={e => trackEvent('Sync', 'Show Menu', 'Authenticated')}>
|
||||
<DropdownButton className="btn btn--compact wide" onClick={this._trackShowMenu}>
|
||||
{description}
|
||||
</DropdownButton>
|
||||
<DropdownDivider name={`Workspace Synced ${syncPercent}%`}/>
|
||||
<DropdownItem onClick={e => this._handleToggleSyncMode(resourceGroupId)}
|
||||
<DropdownDivider>Workspace Synced {syncPercent}%</DropdownDivider>
|
||||
<DropdownItem onClick={this._handleToggleSyncMode}
|
||||
stayOpenAfterClick={true}>
|
||||
{syncMode === syncStorage.SYNC_MODE_OFF ?
|
||||
<i className="fa fa-toggle-off"></i> :
|
||||
<i className="fa fa-toggle-on"></i>}
|
||||
Automatic Sync
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={e => this._handleSyncResourceGroupId(resourceGroupId)}
|
||||
<DropdownItem onClick={this._handleSyncResourceGroupId}
|
||||
disabled={syncPercent === 100}
|
||||
stayOpenAfterClick={true}>
|
||||
{loading ?
|
||||
@ -185,13 +150,8 @@ class SyncDropdown extends Component {
|
||||
Sync Now {syncPercent === 100 ? '(up to date)' : ''}
|
||||
</DropdownItem>
|
||||
|
||||
<DropdownDivider name="Other"/>
|
||||
|
||||
<DropdownItem onClick={e => showModal(SettingsModal, TAB_PLUS)}>
|
||||
<i className="fa fa-user"></i>
|
||||
Manage Account
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={e => showModal(SyncLogsModal)}>
|
||||
<DropdownDivider>Other</DropdownDivider>
|
||||
<DropdownItem onClick={this._handleShowLogs}>
|
||||
<i className="fa fa-bug"></i>
|
||||
Show Debug Logs
|
||||
</DropdownItem>
|
||||
|
@ -10,6 +10,7 @@ import {showModal} from '../modals/index';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import Link from '../base/Link';
|
||||
import WorkspaceSettingsModal from '../modals/WorkspaceSettingsModal';
|
||||
import WorkspaceShareSettingsModal from '../modals/WorkspaceShareSettingsModal';
|
||||
|
||||
class WorkspaceDropdown extends Component {
|
||||
_handleShowExport = () => showModal(SettingsModal, TAB_INDEX_EXPORT);
|
||||
@ -19,6 +20,11 @@ class WorkspaceDropdown extends Component {
|
||||
workspace: this.props.activeWorkspace,
|
||||
});
|
||||
};
|
||||
_handleShowShareSettings = () => {
|
||||
showModal(WorkspaceShareSettingsModal, {
|
||||
workspace: this.props.activeWorkspace,
|
||||
});
|
||||
};
|
||||
|
||||
_handleSwitchWorkspace = workspaceId => {
|
||||
this.props.handleSetActiveWorkspace(workspaceId);
|
||||
@ -64,13 +70,16 @@ class WorkspaceDropdown extends Component {
|
||||
{activeWorkspace.name}
|
||||
</h1>
|
||||
</DropdownButton>
|
||||
<DropdownDivider name={activeWorkspace.name}/>
|
||||
<DropdownDivider>{activeWorkspace.name}</DropdownDivider>
|
||||
<DropdownItem onClick={this._handleShowWorkspaceSettings}>
|
||||
<i className="fa fa-wrench"/> Workspace Settings
|
||||
<DropdownHint char="⇧,"/>
|
||||
</DropdownItem>
|
||||
{/*<DropdownItem onClick={this._handleShowShareSettings}>*/}
|
||||
{/*<i className="fa fa-user"/> Share <strong>{activeWorkspace.name}</strong>*/}
|
||||
{/*</DropdownItem>*/}
|
||||
|
||||
<DropdownDivider name="Switch Workspace"/>
|
||||
<DropdownDivider>Switch Workspace</DropdownDivider>
|
||||
|
||||
{nonActiveWorkspaces.map(w => (
|
||||
<DropdownItem key={w._id} onClick={this._handleSwitchWorkspace} value={w._id}>
|
||||
@ -81,10 +90,10 @@ class WorkspaceDropdown extends Component {
|
||||
<i className="fa fa-empty"/> New Workspace
|
||||
</DropdownItem>
|
||||
|
||||
<DropdownDivider name={`Insomnia Version ${getAppVersion()}`}/>
|
||||
<DropdownDivider>Insomnia Version {getAppVersion()}</DropdownDivider>
|
||||
|
||||
<DropdownItem onClick={this._handleShowSettings}>
|
||||
<i className="fa fa-cog"/> Preferences
|
||||
<i className="fa fa-cog"/> App Settings
|
||||
<DropdownHint char=","/>
|
||||
</DropdownItem>
|
||||
<DropdownItem onClick={this._handleShowExport}>
|
||||
|
@ -97,23 +97,18 @@ class CookiesModal extends Component {
|
||||
const {filter} = this.state;
|
||||
|
||||
return (
|
||||
<Modal ref={m => this.modal = m} wide={true} top={true}
|
||||
tall={true} {...this.props}>
|
||||
<ModalHeader>
|
||||
Manage Cookies
|
||||
</ModalHeader>
|
||||
<Modal ref={m => this.modal = m} wide={true} top={true} tall={true} {...this.props}>
|
||||
<ModalHeader>Manage Cookies</ModalHeader>
|
||||
<ModalBody className="cookie-editor">
|
||||
<div>
|
||||
<div
|
||||
className="cookie-editor__filter form-control form-control--outlined">
|
||||
<label className="label--small">Filter Cookies</label>
|
||||
<input
|
||||
ref={n => this.filterInput = n}
|
||||
onChange={e => this._onFilterChange(e.target.value)}
|
||||
type="text"
|
||||
placeholder="twitter.com"
|
||||
defaultValue=""
|
||||
/>
|
||||
<div className="pad">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Filter Cookies
|
||||
<input ref={n => this.filterInput = n}
|
||||
onChange={e => this._onFilterChange(e.target.value)}
|
||||
type="text"
|
||||
placeholder="twitter.com"
|
||||
defaultValue=""/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cookie-editor__editor border-top">
|
||||
|
@ -5,10 +5,7 @@ import ModalBody from '../base/ModalBody';
|
||||
import ModalHeader from '../base/ModalHeader';
|
||||
import ModalFooter from '../base/ModalFooter';
|
||||
import * as session from '../../../sync/session';
|
||||
import {showModal} from './index';
|
||||
import SignupModal from './SignupModal';
|
||||
import * as sync from '../../../sync';
|
||||
import {trackEvent} from '../../../analytics';
|
||||
|
||||
class LoginModal extends Component {
|
||||
state = {
|
||||
@ -41,14 +38,6 @@ class LoginModal extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
_handleSignup = e => {
|
||||
e.preventDefault();
|
||||
|
||||
this.modal.hide();
|
||||
showModal(SignupModal);
|
||||
trackEvent('Login', 'Switch to Signup');
|
||||
};
|
||||
|
||||
show (options = {}) {
|
||||
const {title, message} = options;
|
||||
this.setState({step: 1, error: '', loading: false, title, message});
|
||||
@ -92,7 +81,7 @@ class LoginModal extends Component {
|
||||
<div className="margin-left">
|
||||
Don't have an account yet?
|
||||
{" "}
|
||||
<a href="#" onClick={this._handleSignup}>Signup</a>
|
||||
<Link href="https://insomnia.rest/app/">Signup</Link>
|
||||
</div>
|
||||
<button type="submit" className="btn">
|
||||
{this.state.loading ? (
|
||||
|
@ -1,297 +0,0 @@
|
||||
import React, {Component} from 'react';
|
||||
import Link from '../base/Link';
|
||||
import Nl2Br from '../Nl2Br';
|
||||
import Modal from '../base/Modal';
|
||||
import ModalBody from '../base/ModalBody';
|
||||
import ModalHeader from '../base/ModalHeader';
|
||||
import ModalFooter from '../base/ModalFooter';
|
||||
import * as session from '../../../sync/session';
|
||||
import AlertModal from './AlertModal';
|
||||
import {showModal} from './index';
|
||||
|
||||
const MONTHS = [
|
||||
{name: 'January', value: '01'},
|
||||
{name: 'February', value: '02'},
|
||||
{name: 'March', value: '03'},
|
||||
{name: 'April', value: '04'},
|
||||
{name: 'May', value: '05'},
|
||||
{name: 'June', value: '06'},
|
||||
{name: 'July', value: '07'},
|
||||
{name: 'August', value: '08'},
|
||||
{name: 'September', value: '09'},
|
||||
{name: 'October', value: '10'},
|
||||
{name: 'November', value: '11'},
|
||||
{name: 'December', value: '12'},
|
||||
];
|
||||
|
||||
const YEARS = [];
|
||||
for (let i = 0; i < 20; i++) {
|
||||
YEARS.push(2016 + i);
|
||||
}
|
||||
|
||||
class PaymentModal extends Component {
|
||||
state = {
|
||||
title: '',
|
||||
message: '',
|
||||
error: '',
|
||||
loading: false,
|
||||
cardType: '',
|
||||
selectedPlan: 'plus-monthly-1'
|
||||
};
|
||||
|
||||
show (options = {}) {
|
||||
const {message, title} = options;
|
||||
this.setState({error: '', message, title});
|
||||
this.modal.show();
|
||||
setTimeout(() => this._nameInput.focus(), 100);
|
||||
}
|
||||
|
||||
hide () {
|
||||
this.modal.hide();
|
||||
}
|
||||
|
||||
_handlePlanChange (selectedPlan) {
|
||||
this.setState({selectedPlan});
|
||||
}
|
||||
|
||||
_handleCreditCardCvcChange (e) {
|
||||
const value = e.target.value.trim();
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.target.value = value.replace(/[^0-9]/g, '').trim()
|
||||
}
|
||||
|
||||
_handleCreditCardNumberChange (e) {
|
||||
const value = e.target.value.trim();
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cardType = Stripe.card.cardType(value);
|
||||
const lastChar = value[e.target.value.length - 1];
|
||||
const num = value.replace(/[^0-9]*/g, '');
|
||||
let newNum = '';
|
||||
|
||||
if (cardType === 'American Express') {
|
||||
// 1111 222222 33333
|
||||
const g1 = num.slice(0, 4);
|
||||
const g2 = num.slice(4, 10);
|
||||
const g3 = num.slice(10, 15);
|
||||
|
||||
newNum = g1;
|
||||
newNum += g2 ? ` ${g2}` : '';
|
||||
newNum += g3 ? ` ${g3}` : '';
|
||||
} else if (cardType === 'Diners Club') {
|
||||
// 1111 2222 3333 44
|
||||
const g1 = num.slice(0, 4);
|
||||
const g2 = num.slice(4, 8);
|
||||
const g3 = num.slice(8, 12);
|
||||
const g4 = num.slice(12, 14);
|
||||
|
||||
newNum = g1;
|
||||
newNum += g2 ? ` ${g2}` : '';
|
||||
newNum += g3 ? ` ${g3}` : '';
|
||||
newNum += g4 ? ` ${g4}` : '';
|
||||
} else {
|
||||
// 1111 2222 3333 4444
|
||||
const g1 = num.slice(0, 4);
|
||||
const g2 = num.slice(4, 8);
|
||||
const g3 = num.slice(8, 12);
|
||||
const g4 = num.slice(12, 16);
|
||||
|
||||
newNum = g1;
|
||||
newNum += g2 ? ` ${g2}` : '';
|
||||
newNum += g3 ? ` ${g3}` : '';
|
||||
newNum += g4 ? ` ${g4}` : '';
|
||||
}
|
||||
|
||||
// Handle trailing dash so we can add and delete dashes properly
|
||||
if (lastChar === ' ') {
|
||||
newNum += ' ';
|
||||
}
|
||||
|
||||
this.setState({cardType: cardType === 'Unknown' ? '' : cardType});
|
||||
|
||||
// Only update number if it changed from the user's original to prevent cursor jump
|
||||
if (newNum !== value) {
|
||||
e.target.value = newNum;
|
||||
}
|
||||
}
|
||||
|
||||
_handleSubmit (e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.setState({error: '', loading: true});
|
||||
|
||||
const params = {
|
||||
name: this._nameInput.value,
|
||||
number: this._cardNumberInput.value.replace(/ /g, ''),
|
||||
cvc: this._cvcInput.value,
|
||||
exp_month: parseInt(this._expiryMonthInput.value, 10),
|
||||
exp_year: parseInt(this._expiryYearInput.value, 10),
|
||||
};
|
||||
|
||||
Stripe.card.createToken(params, async (status, response) => {
|
||||
if (status === 200) {
|
||||
try {
|
||||
await session.subscribe(response.id, this.state.selectedPlan);
|
||||
this.setState({error: '', loading: false});
|
||||
showModal(AlertModal, {
|
||||
title: 'Payment Succeeded',
|
||||
message: 'Thanks for supporting Insomnia! You may now continue using the app.'
|
||||
});
|
||||
this.hide();
|
||||
} catch (e) {
|
||||
this.setState({error: e.message, loading: false});
|
||||
}
|
||||
} else {
|
||||
this.setState({error: response.error.message, loading: false});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const {selectedPlan, cardType, message, title} = this.state;
|
||||
|
||||
return (
|
||||
<form onSubmit={this._handleSubmit.bind(this)}
|
||||
key={session.getCurrentSessionId()}>
|
||||
<Modal ref={m => this.modal = m} {...this.props}>
|
||||
<ModalHeader>{title || "Enter Payment Method"}</ModalHeader>
|
||||
<ModalBody className="pad changelog">
|
||||
<div style={{maxWidth: '32rem', margin: 'auto'}}>
|
||||
{message ? (
|
||||
<Nl2Br className="notice info">{message}</Nl2Br>
|
||||
) : null}
|
||||
<div>
|
||||
<div className="pad-left-sm">
|
||||
<div className="inline-block text-center"
|
||||
style={{width: '50%'}}>
|
||||
<input
|
||||
id="plan-plus-monthly-1"
|
||||
type="radio"
|
||||
name="payment-cycle"
|
||||
checked={selectedPlan === 'plus-monthly-1'}
|
||||
onChange={e => this._handlePlanChange('plus-monthly-1')}
|
||||
/>
|
||||
|
||||
<label htmlFor="plan-plus-monthly-1">
|
||||
Per Month ($5/mo)
|
||||
</label>
|
||||
</div>
|
||||
<div className="inline-block text-center"
|
||||
style={{width: '50%'}}>
|
||||
<input
|
||||
id="plan-plus-yearly-1"
|
||||
type="radio"
|
||||
name="payment-cycle"
|
||||
checked={selectedPlan === 'plus-yearly-1'}
|
||||
onChange={e => this._handlePlanChange('plus-yearly-1')}
|
||||
/>
|
||||
|
||||
<label htmlFor="plan-plus-yearly-1">Annual ($50/yr)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pad-top">
|
||||
<label htmlFor="payment-name">Full name</label>
|
||||
<div className="form-control form-control--outlined">
|
||||
<input type="text"
|
||||
required="required"
|
||||
id="payment-name"
|
||||
name="payment-name"
|
||||
placeholder="Paula Jones"
|
||||
defaultValue={session.getFullName()}
|
||||
ref={n => this._nameInput = n}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="payment-card-number">
|
||||
Card Number {cardType ? `(${cardType})` : ''}
|
||||
</label>
|
||||
<div className="form-control form-control--outlined">
|
||||
<input type="text"
|
||||
required="required"
|
||||
id="payment-card-number"
|
||||
placeholder="4012 8888 8888 1881"
|
||||
onChange={this._handleCreditCardNumberChange.bind(this)}
|
||||
ref={n => this._cardNumberInput = n}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="inline-block" style={{width: '66%'}}>
|
||||
<label htmlFor="payment-expiry">
|
||||
Expiration Date
|
||||
</label>
|
||||
<div className="pad-left-sm pad-top-sm">
|
||||
<select name="payment-expiration-month"
|
||||
id="payment-expiration-month"
|
||||
ref={n => this._expiryMonthInput = n}>
|
||||
{MONTHS.map(({name, value}) => (
|
||||
<option key={value} value={value}>
|
||||
{value} – {name}</option>
|
||||
))}
|
||||
</select>
|
||||
{" "}
|
||||
<select name="payment-expiration-year"
|
||||
id="payment-expiration-year"
|
||||
ref={n => this._expiryYearInput = n}>
|
||||
{YEARS.map(year => (
|
||||
<option key={year} value={year}>{year}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="inline-block" style={{width: '34%'}}>
|
||||
<label htmlFor="payment-cvc">Security Code (CVC)</label>
|
||||
<div className="form-control form-control--outlined">
|
||||
<input type="text"
|
||||
maxLength="4"
|
||||
required="required"
|
||||
id="payment-cvc"
|
||||
name="payment-cvc"
|
||||
placeholder="013"
|
||||
onChange={this._handleCreditCardCvcChange.bind(this)}
|
||||
ref={n => this._cvcInput = n}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.error ? (
|
||||
<div className="danger pad-top">** {this.state.error}</div>
|
||||
) : null}
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<div className="margin-left faint italic txt-sm">
|
||||
* Payments secured and managed by
|
||||
{" "}
|
||||
<Link href="https://stripe.com">Stripe</Link>
|
||||
</div>
|
||||
{this.state.loading ? (
|
||||
<button type="submit" className="btn">
|
||||
<i className="fa fa-spin fa-refresh margin-right-sm"></i>
|
||||
{" "}
|
||||
Processing
|
||||
</button>
|
||||
) : (
|
||||
<button type="submit" className="btn">
|
||||
Submit Details
|
||||
</button>
|
||||
)}
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PaymentModal.propTypes = {};
|
||||
|
||||
export default PaymentModal;
|
@ -1,10 +1,9 @@
|
||||
import React, {Component} from 'react';
|
||||
import Link from '../base/Link';
|
||||
import Modal from '../base/Modal';
|
||||
import ModalBody from '../base/ModalBody';
|
||||
import ModalHeader from '../base/ModalHeader';
|
||||
import ModalFooter from '../base/ModalFooter';
|
||||
import PaymentModal from './PaymentModal';
|
||||
import {showModal} from './index';
|
||||
import * as session from '../../../sync/session';
|
||||
import {trackEvent} from '../../../analytics';
|
||||
|
||||
@ -16,12 +15,6 @@ class PaymentNotificationModal extends Component {
|
||||
this.hide();
|
||||
};
|
||||
|
||||
_handleProceedToPayment = () => {
|
||||
this.hide();
|
||||
showModal(PaymentModal);
|
||||
trackEvent('Billing', 'Trial Ended', 'Proceed')
|
||||
};
|
||||
|
||||
show () {
|
||||
// Don't trigger automatically if user has dismissed it already
|
||||
if (hidePaymentNotificationUntilNextLaunch) {
|
||||
@ -52,10 +45,9 @@ class PaymentNotificationModal extends Component {
|
||||
</p>
|
||||
<br/>
|
||||
<p className="pad-top">
|
||||
<button className="btn btn--compact btn--outlined"
|
||||
onClick={this._handleProceedToPayment}>
|
||||
Proceed to Billing
|
||||
</button>
|
||||
<Link button={true} href="https://insomnia.rest/app/subscribe/" className="btn btn--compact btn--outlined">
|
||||
Enter Billing Info
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</ModalBody>
|
||||
@ -63,7 +55,7 @@ class PaymentNotificationModal extends Component {
|
||||
<button className="btn" onClick={this._handleHide}>
|
||||
Maybe Later
|
||||
</button>
|
||||
<div></div>
|
||||
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
)
|
||||
|
@ -87,7 +87,7 @@ class RequestCreateModal extends Component {
|
||||
<Modal ref={m => this.modal = m}>
|
||||
<ModalHeader>New Request</ModalHeader>
|
||||
<ModalBody noScroll={true}>
|
||||
<form onSubmit={this._handleSubmit} className="pad no-pad-top">
|
||||
<form onSubmit={this._handleSubmit} className="pad">
|
||||
<div className="row-fill">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Name
|
||||
@ -95,14 +95,13 @@ class RequestCreateModal extends Component {
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control" style={{width: 'auto'}}>
|
||||
<label htmlFor="nothing">
|
||||
<MethodDropdown
|
||||
className="btn btn--clicky no-wrap"
|
||||
right={true}
|
||||
method={selectedMethod}
|
||||
onChange={this._handleChangeSelectedMethod}
|
||||
/>
|
||||
</label>
|
||||
<label> </label>
|
||||
<MethodDropdown
|
||||
className="btn btn--clicky no-wrap"
|
||||
right={true}
|
||||
method={selectedMethod}
|
||||
onChange={this._handleChangeSelectedMethod}
|
||||
/>
|
||||
</div>
|
||||
{!this._shouldNotHaveBody() ? (
|
||||
<div className="form-control" style={{width: 'auto'}}>
|
||||
|
@ -4,7 +4,6 @@ import {shell} from 'electron';
|
||||
import Modal from '../base/Modal';
|
||||
import ModalBody from '../base/ModalBody';
|
||||
import ModalHeader from '../base/ModalHeader';
|
||||
import ModalFooter from '../base/ModalFooter';
|
||||
import SettingsShortcuts from '../settings/SettingsShortcuts';
|
||||
import SettingsAbout from '../settings/SettingsAbout';
|
||||
import SettingsGeneral from '../settings/SettingsGeneral';
|
||||
@ -14,7 +13,6 @@ import * as models from '../../../models';
|
||||
import {getAppVersion, getAppName} from '../../../common/constants';
|
||||
import * as session from '../../../sync/session';
|
||||
import {showModal} from './index';
|
||||
import SignupModal from './SignupModal';
|
||||
import * as sync from '../../../sync';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
|
||||
@ -82,7 +80,8 @@ class SettingsModal extends Component {
|
||||
<button onClick={e => trackEvent('Setting', 'Tab General')}>General</button>
|
||||
</Tab>
|
||||
<Tab selected={this._currentTabIndex === 1}>
|
||||
<button onClick={e => trackEvent('Setting', 'Tab Import/Export')}>Import/Export</button>
|
||||
<button onClick={e => trackEvent('Setting', 'Tab Import/Export')}>Import/Export
|
||||
</button>
|
||||
</Tab>
|
||||
<Tab selected={this._currentTabIndex === 3}>
|
||||
<button onClick={e => trackEvent('Setting', 'Tab Shortcuts')}>Shortcuts</button>
|
||||
@ -126,9 +125,8 @@ class SettingsModal extends Component {
|
||||
<SettingsSync
|
||||
loggedIn={session.isLoggedIn()}
|
||||
firstName={session.getFirstName() || ''}
|
||||
handleExit={() => this.modal.hide()}
|
||||
handleUpdateSetting={(key, value) => models.settings.update(settings, {[key]: value})}
|
||||
handleShowSignup={() => showModal(SignupModal)}
|
||||
email={session.getEmail() || ''}
|
||||
handleExit={this._handleClose}
|
||||
handleCancelAccount={sync.cancelAccount}
|
||||
handleReset={() => this._handleSyncReset()}
|
||||
handleLogout={sync.logout}
|
||||
@ -139,11 +137,6 @@ class SettingsModal extends Component {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</ModalBody>
|
||||
{/*<ModalFooter>*/}
|
||||
{/*<button className="btn" onClick={this._handleClose}>*/}
|
||||
{/*Close*/}
|
||||
{/*</button>*/}
|
||||
{/*</ModalFooter>*/}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -1,195 +0,0 @@
|
||||
import React, {Component} from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Link from '../base/Link';
|
||||
import Modal from '../base/Modal';
|
||||
import ModalBody from '../base/ModalBody';
|
||||
import ModalHeader from '../base/ModalHeader';
|
||||
import ModalFooter from '../base/ModalFooter';
|
||||
import * as session from '../../../sync/session';
|
||||
import {showModal} from './index';
|
||||
import LoginModal from './LoginModal';
|
||||
import * as sync from '../../../sync';
|
||||
import {trackEvent} from '../../../analytics';
|
||||
|
||||
const STEP_BASIC_INFO = 'basic';
|
||||
const STEP_CONFIRM_PASSWORD = 'confirm';
|
||||
const STEP_LOGIN_INFO = 'done';
|
||||
|
||||
class SignupModal extends Component {
|
||||
state = {
|
||||
step: STEP_BASIC_INFO,
|
||||
error: '',
|
||||
loading: false
|
||||
};
|
||||
|
||||
async _handleSignup (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (this.state.step === STEP_BASIC_INFO) {
|
||||
this.setState({step: STEP_CONFIRM_PASSWORD});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({error: '', loading: true});
|
||||
|
||||
const email = this._emailInput.value;
|
||||
const password = this._passwordInput.value;
|
||||
const firstName = this._nameFirstInput.value;
|
||||
const lastName = this._nameLastInput.value;
|
||||
|
||||
try {
|
||||
await session.signup(firstName, lastName, email, password);
|
||||
this.setState({step: STEP_LOGIN_INFO, loading: false});
|
||||
sync.init();
|
||||
} catch (e) {
|
||||
this.setState({error: e.message, loading: false});
|
||||
}
|
||||
}
|
||||
|
||||
_handleLogin (e) {
|
||||
e.preventDefault();
|
||||
|
||||
this.modal.hide();
|
||||
showModal(LoginModal, {});
|
||||
trackEvent('Signup', 'Switch to Login');
|
||||
}
|
||||
|
||||
_checkPasswordsMatch () {
|
||||
if (this._passwordInput.value !== this._passwordConfirmInput.value) {
|
||||
this._passwordConfirmInput.setCustomValidity('Password didn\'t match')
|
||||
} else {
|
||||
this._passwordConfirmInput.setCustomValidity('')
|
||||
}
|
||||
}
|
||||
|
||||
show () {
|
||||
this.setState({step: STEP_BASIC_INFO, loading: false, error: ''});
|
||||
this.modal.show();
|
||||
setTimeout(() => this._nameFirstInput.focus(), 200);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {step} = this.state;
|
||||
|
||||
let inner = null;
|
||||
if (step === STEP_BASIC_INFO || step === STEP_CONFIRM_PASSWORD) {
|
||||
inner = [
|
||||
<ModalHeader key="header">Sign Up For a New Account</ModalHeader>,
|
||||
<ModalBody key="body" className="pad">
|
||||
<div className={classnames({hide: step !== STEP_BASIC_INFO})}>
|
||||
<div className="form-control form-control--outlined no-pad-top">
|
||||
<label>First Name
|
||||
<input type="text"
|
||||
required="required"
|
||||
name="signup-name-first"
|
||||
placeholder="Jane"
|
||||
ref={n => this._nameFirstInput = n}/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Last Name
|
||||
<input type="text"
|
||||
name="signup-name-last"
|
||||
placeholder="Doe"
|
||||
ref={n => this._nameLastInput = n}/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Email Address
|
||||
<input type="email"
|
||||
required="required"
|
||||
name="signup-email"
|
||||
placeholder="me@mydomain.com"
|
||||
ref={n => this._emailInput = n}/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Password <span className="faint">(minimum 6 characters)</span>
|
||||
<input type="password"
|
||||
required="required"
|
||||
pattern=".{6,}"
|
||||
name="signup-password"
|
||||
placeholder="•••••••••••••"
|
||||
ref={n => this._passwordInput = n}/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{step === STEP_CONFIRM_PASSWORD ? (
|
||||
<div className="text-center pad">
|
||||
<p className="notice info text-center txt-lg">
|
||||
Keep your password safe because it cannot be recovered
|
||||
<br/>
|
||||
<span className="txt-sm italic">
|
||||
<Link href="https://insomnia.rest/documentation/plus/">Read More</Link>
|
||||
{" "}
|
||||
about how your password is used to encrypt your data
|
||||
</span>
|
||||
</p>
|
||||
<div className="text-left">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label className="pad-left-sm">Confirm your Password
|
||||
<input type="password"
|
||||
required="required"
|
||||
pattern=".{6,}"
|
||||
id="signup-password-confirm"
|
||||
name="signup-password-confirm"
|
||||
placeholder="•••••••••••••"
|
||||
autoFocus="autoFocus"
|
||||
onChange={this._checkPasswordsMatch.bind(this)}
|
||||
ref={n => this._passwordConfirmInput = n}/>
|
||||
</label>
|
||||
</div>
|
||||
<button type="button"
|
||||
className="pad-sm faint"
|
||||
onClick={e => this.setState({step: STEP_BASIC_INFO})}>
|
||||
<i className="fa fa-caret-left"/>
|
||||
Go back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{this.state.error ? (
|
||||
<div className="danger pad-top">** {this.state.error}</div>
|
||||
) : null}
|
||||
</ModalBody>,
|
||||
<ModalFooter key="footer">
|
||||
<div className="margin-left">
|
||||
Already have an account?
|
||||
{" "}
|
||||
<a href="#" onClick={this._handleLogin.bind(this)}>Login</a>
|
||||
</div>
|
||||
<button type="submit" className="btn">
|
||||
{this.state.loading ? <i className="fa fa-spin fa-refresh margin-right-sm"/> : null}
|
||||
Create Account
|
||||
</button>
|
||||
</ModalFooter>
|
||||
]
|
||||
} else {
|
||||
inner = [
|
||||
<ModalHeader key="header">Account Created</ModalHeader>,
|
||||
<ModalBody key="body" className="pad no-pad-top">
|
||||
<h1>Thanks for signing up!</h1>
|
||||
<p>A verification email has been sent to your email address. You may now log in.</p>
|
||||
</ModalBody>,
|
||||
<ModalFooter key="footer">
|
||||
<button type="submit"
|
||||
className="btn"
|
||||
onClick={e => this._handleLogin(e)}>
|
||||
Proceed to Login
|
||||
</button>
|
||||
</ModalFooter>
|
||||
]
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={this._handleSignup.bind(this)}>
|
||||
<Modal ref={m => this.modal = m} {...this.props}>
|
||||
{inner}
|
||||
</Modal>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SignupModal.propTypes = {};
|
||||
export default SignupModal;
|
@ -250,8 +250,8 @@ class WorkspaceSettingsModal extends Component {
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="row-fill">
|
||||
<div className="form-control">
|
||||
<div className="form-row">
|
||||
<div className="form-control width-auto">
|
||||
<label>PFX <span className="faint">(or PKCS12)</span>
|
||||
<FileInputButton
|
||||
className="btn btn--clicky"
|
||||
@ -261,11 +261,11 @@ class WorkspaceSettingsModal extends Component {
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="width-auto">
|
||||
<div className="text-center">
|
||||
<br/><br/>
|
||||
Or
|
||||
</div>
|
||||
<div className="no-wrap row-fill">
|
||||
<div className="row-fill">
|
||||
<div className="form-control">
|
||||
<label>CRT File
|
||||
<FileInputButton
|
||||
|
95
app/ui/components/modals/WorkspaceShareSettingsModal.js
Normal file
95
app/ui/components/modals/WorkspaceShareSettingsModal.js
Normal file
@ -0,0 +1,95 @@
|
||||
import React, {Component, PropTypes} from 'react';
|
||||
import {Dropdown, DropdownButton, DropdownItem, DropdownDivider} from '../base/dropdown';
|
||||
import Link from '../base/Link';
|
||||
import Modal from '../base/Modal';
|
||||
import ModalBody from '../base/ModalBody';
|
||||
import ModalHeader from '../base/ModalHeader';
|
||||
import ModalFooter from '../base/ModalFooter';
|
||||
import * as session from '../../../sync/session';
|
||||
import LoginModal from './LoginModal';
|
||||
import {showModal} from './index';
|
||||
|
||||
class WorkspaceShareSettingsModal extends Component {
|
||||
state = {
|
||||
teams: []
|
||||
};
|
||||
|
||||
_handleShareWithTeam = team => {
|
||||
console.log('Share with ', team);
|
||||
};
|
||||
|
||||
async _load () {
|
||||
if (!session.isLoggedIn()) {
|
||||
showModal(LoginModal);
|
||||
}
|
||||
|
||||
const teams = await session.listTeams();
|
||||
this.setState({teams});
|
||||
}
|
||||
|
||||
_handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
_handleClose = () => this.hide();
|
||||
|
||||
_setModalRef = m => {
|
||||
this.modal = m;
|
||||
};
|
||||
|
||||
show () {
|
||||
this.modal.show();
|
||||
this._load();
|
||||
}
|
||||
|
||||
render () {
|
||||
const {teams} = this.state;
|
||||
|
||||
return (
|
||||
<form onSubmit={this._handleSubmit}>
|
||||
<Modal ref={this._setModalRef}>
|
||||
<ModalHeader key="header">Share Workspace</ModalHeader>
|
||||
<ModalBody key="body" className="pad" noScroll={true}>
|
||||
<p>
|
||||
Enabling sync will automatically share your workspace with your entire team
|
||||
</p>
|
||||
<div className="text-center form-control pad">
|
||||
<Dropdown outline={true}>
|
||||
<DropdownDivider>Teams</DropdownDivider>
|
||||
<DropdownButton type="button" className="btn btn--clicky">
|
||||
<i className="fa fa-lock"/> Private <i className="fa fa-caret-down"/>
|
||||
</DropdownButton>
|
||||
{teams.map(team => (
|
||||
<DropdownItem key={team._id} onClick={e => this._handleShareWithTeam(team)}>
|
||||
<i className="fa fa-users"/> Share with <strong>{team.name}</strong>
|
||||
</DropdownItem>
|
||||
))}
|
||||
<DropdownDivider>Other</DropdownDivider>
|
||||
<DropdownItem>
|
||||
<i className="fa fa-lock"/> Private
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
|
||||
<Link button={true}
|
||||
className="btn btn--super-compact inline-block"
|
||||
href="https://insomnia.rest/app/teams/">
|
||||
Manage Teams
|
||||
</Link>
|
||||
</div>
|
||||
</ModalBody>
|
||||
<ModalFooter key="footer">
|
||||
<button type="button" className="btn" onClick={this._handleClose}>
|
||||
Done
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WorkspaceShareSettingsModal.propTypes = {
|
||||
workspace: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default WorkspaceShareSettingsModal;
|
@ -2,130 +2,88 @@ import React, {PropTypes} from 'react';
|
||||
|
||||
const SettingsGeneral = ({settings, updateSetting}) => (
|
||||
<div>
|
||||
<div>
|
||||
<input
|
||||
id="setting-follow-redirects"
|
||||
type="checkbox"
|
||||
checked={settings.followRedirects}
|
||||
onChange={e => updateSetting('followRedirects', e.target.checked)}
|
||||
/>
|
||||
|
||||
<label htmlFor="setting-follow-redirects">
|
||||
Follow redirects automatically
|
||||
<div className="form-control form-control--thin">
|
||||
<label>Follow redirects automatically
|
||||
<input type="checkbox"
|
||||
checked={settings.followRedirects}
|
||||
onChange={e => updateSetting('followRedirects', e.target.checked)}/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
id="setting-validate-ssl"
|
||||
type="checkbox"
|
||||
checked={settings.validateSSL}
|
||||
onChange={e => updateSetting('validateSSL', e.target.checked)}
|
||||
/>
|
||||
|
||||
<label htmlFor="setting-validate-ssl">
|
||||
Validate SSL Certificates
|
||||
<div className="form-control form-control--thin">
|
||||
<label>Validate SSL Certificates
|
||||
<input type="checkbox"
|
||||
checked={settings.validateSSL}
|
||||
onChange={e => updateSetting('validateSSL', e.target.checked)}/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
id="setting-show-passwords"
|
||||
type="checkbox"
|
||||
checked={settings.showPasswords}
|
||||
onChange={e => updateSetting('showPasswords', e.target.checked)}
|
||||
/>
|
||||
|
||||
<label htmlFor="setting-show-passwords">
|
||||
Show passwords in plain-text
|
||||
<div className="form-control form-control--thin">
|
||||
<label>Show passwords in plain-text
|
||||
<input type="checkbox"
|
||||
checked={settings.showPasswords}
|
||||
onChange={e => updateSetting('showPasswords', e.target.checked)}/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
id="setting-bulk-header-editor"
|
||||
type="checkbox"
|
||||
checked={settings.useBulkHeaderEditor}
|
||||
onChange={e => updateSetting('useBulkHeaderEditor', e.target.checked)}
|
||||
/>
|
||||
|
||||
<label htmlFor="setting-bulk-header-editor">
|
||||
Use bulk header editor by default
|
||||
<div className="form-control form-control--thin">
|
||||
<label>Use bulk header editor by default
|
||||
<input type="checkbox"
|
||||
checked={settings.useBulkHeaderEditor}
|
||||
onChange={e => updateSetting('useBulkHeaderEditor', e.target.checked)}/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
id="setting-stacked-layout"
|
||||
type="checkbox"
|
||||
checked={settings.forceVerticalLayout}
|
||||
onChange={e => updateSetting('forceVerticalLayout', e.target.checked)}
|
||||
/>
|
||||
|
||||
<label htmlFor="setting-stacked-layout">
|
||||
Always use vertical layout
|
||||
<div className="form-control form-control--thin">
|
||||
<label>Always use vertical layout
|
||||
<input type="checkbox"
|
||||
checked={settings.forceVerticalLayout}
|
||||
onChange={e => updateSetting('forceVerticalLayout', e.target.checked)}/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input
|
||||
id="setting-editor-line-wrapping"
|
||||
type="checkbox"
|
||||
checked={settings.editorLineWrapping}
|
||||
onChange={e => updateSetting('editorLineWrapping', e.target.checked)}
|
||||
/>
|
||||
|
||||
<label htmlFor="setting-editor-line-wrapping">
|
||||
Wrap Long Lines
|
||||
<div className="form-control form-control--thin">
|
||||
<label>Wrap Long Lines
|
||||
<input type="checkbox"
|
||||
checked={settings.editorLineWrapping}
|
||||
onChange={e => updateSetting('editorLineWrapping', e.target.checked)}/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control form-control--outlined">
|
||||
<div className="form-control form-control--outlined pad-top-sm">
|
||||
<label>Request/Response Font Size (px)
|
||||
<input
|
||||
type="number"
|
||||
min={8}
|
||||
max={20}
|
||||
value={settings.editorFontSize}
|
||||
onChange={e => updateSetting('editorFontSize', parseInt(e.target.value, 10))}
|
||||
/>
|
||||
<input type="number"
|
||||
min={8}
|
||||
max={20}
|
||||
value={settings.editorFontSize}
|
||||
onChange={e => updateSetting('editorFontSize', parseInt(e.target.value, 10))}/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>Request Timeout (ms) (0 for no timeout)
|
||||
<input
|
||||
type="number"
|
||||
min={0}
|
||||
value={settings.timeout}
|
||||
onChange={e => updateSetting('timeout', parseInt(e.target.value, 10))}
|
||||
/>
|
||||
<input type="number"
|
||||
min={0}
|
||||
value={settings.timeout}
|
||||
onChange={e => updateSetting('timeout', parseInt(e.target.value, 10))}/>
|
||||
</label>
|
||||
<div className="inline-block" style={{width: '50%'}}>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>HTTP Proxy
|
||||
<input
|
||||
type="text"
|
||||
placeholder="localhost:8005"
|
||||
defaultValue={settings.httpProxy}
|
||||
onChange={e => updateSetting('httpProxy', e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>HTTP Proxy
|
||||
<input type="text"
|
||||
placeholder="localhost:8005"
|
||||
defaultValue={settings.httpProxy}
|
||||
onChange={e => updateSetting('httpProxy', e.target.value)}/>
|
||||
</label>
|
||||
</div>
|
||||
<div className="inline-block" style={{width: '50%'}}>
|
||||
<div className="pad-left-sm">
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>HTTPS Proxy
|
||||
<input
|
||||
placeholder="localhost:8005"
|
||||
type="text"
|
||||
defaultValue={settings.httpsProxy}
|
||||
onChange={e => updateSetting('httpsProxy', e.target.value)}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-control form-control--outlined">
|
||||
<label>HTTPS Proxy
|
||||
<input placeholder="localhost:8005"
|
||||
type="text"
|
||||
defaultValue={settings.httpsProxy}
|
||||
onChange={e => updateSetting('httpsProxy', e.target.value)}/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
|
@ -22,7 +22,7 @@ const SettingsImportExport = ({
|
||||
<DropdownButton className="btn btn--clicky">
|
||||
Export Data <i className="fa fa-caret-down"></i>
|
||||
</DropdownButton>
|
||||
<DropdownDivider name="Choose Export Type"/>
|
||||
<DropdownDivider>Choose Export Type</DropdownDivider>
|
||||
<DropdownItem onClick={handleExportWorkspace}>
|
||||
<i className="fa fa-home"></i>
|
||||
Current Workspace
|
||||
|
@ -1,99 +1,82 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import React, {PropTypes, Component} from 'react';
|
||||
import Link from '../base/Link';
|
||||
import PromptButton from '../base/PromptButton';
|
||||
import {trackEvent} from '../../../analytics/index';
|
||||
import {showModal} from '../modals/index';
|
||||
import PaymentModal from '../modals/PaymentModal';
|
||||
import LoginModal from '../modals/LoginModal';
|
||||
|
||||
const SettingsSync = ({
|
||||
loggedIn,
|
||||
firstName,
|
||||
handleExit,
|
||||
handleUpdateSetting,
|
||||
handleShowSignup,
|
||||
handleCancelAccount,
|
||||
handleLogout,
|
||||
handleReset,
|
||||
}) => (
|
||||
<div>
|
||||
<h1 className="no-margin-top">Cloud Sync and Backup</h1>
|
||||
class SettingsSync extends Component {
|
||||
_handleClickLogout = async () => {
|
||||
this.props.handleExit();
|
||||
await this.props.handleLogout();
|
||||
};
|
||||
|
||||
{loggedIn ? [
|
||||
<p key="1">
|
||||
Hi {firstName}! Thanks for signing up for Insomnia
|
||||
Plus.
|
||||
</p>,
|
||||
<p key="2" className="pad-top">
|
||||
<PromptButton
|
||||
className="btn btn--clicky danger"
|
||||
onClick={() => {
|
||||
handleExit();
|
||||
handleCancelAccount();
|
||||
}}>
|
||||
Cancel Subscription
|
||||
</PromptButton>
|
||||
{" "}
|
||||
<PromptButton className="btn btn--clicky warning"
|
||||
onClick={handleReset}
|
||||
confirmMessage="Delete all sync-related data?">
|
||||
Reset Remote Data
|
||||
</PromptButton>
|
||||
{" "}
|
||||
<button className="btn btn--clicky"
|
||||
onClick={async () => {
|
||||
handleExit();
|
||||
showModal(PaymentModal);
|
||||
}}>
|
||||
Update Billing
|
||||
</button>
|
||||
{" "}
|
||||
<PromptButton className="btn btn--clicky"
|
||||
onClick={async () => {
|
||||
handleExit();
|
||||
await handleLogout();
|
||||
}}>
|
||||
Log Out
|
||||
</PromptButton>
|
||||
</p>
|
||||
] : [
|
||||
<p key="0">
|
||||
<Link href="https://insomnia.rest/plus/">Insomnia Plus</Link> helps you <i>rest</i> easy by
|
||||
keeping your workspaces securely backed up and synced across all of your devices.
|
||||
</p>,
|
||||
<p key="1">
|
||||
Upgrade today to start enjoying
|
||||
{" "}
|
||||
<Link href="https://insomnia.rest/pricing/">all of the benefits</Link>, while also helping
|
||||
support my continuous effort of making Insomnia awesome! <i
|
||||
className="fa fa-smile-o txt-xl"/>
|
||||
</p>,
|
||||
<p key="2" className="pad-top text-center">
|
||||
<button className="btn txt-lg btn--outlined"
|
||||
onClick={() => {
|
||||
handleExit();
|
||||
trackEvent('Settings Sync', 'Click Upgrade');
|
||||
handleShowSignup()
|
||||
}}>
|
||||
Upgrade to Plus
|
||||
</button>
|
||||
</p>,
|
||||
<p key="3" className="text-center italic">
|
||||
$5 per month or $50 per year
|
||||
<br/>
|
||||
<span className="txt-sm faint pad-top-sm">
|
||||
14-day trial (cancel at any time)
|
||||
</span>
|
||||
</p>
|
||||
]}
|
||||
</div>
|
||||
);
|
||||
_handleClickLogin = async () => {
|
||||
this.props.handleExit();
|
||||
showModal(LoginModal);
|
||||
};
|
||||
|
||||
render () {
|
||||
const {
|
||||
loggedIn,
|
||||
firstName,
|
||||
email,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="no-margin-top">Cloud Sync and Backup</h1>
|
||||
|
||||
{loggedIn ? [
|
||||
<p key="1">
|
||||
Hi {firstName}! Thanks for signing up for Insomnia
|
||||
Plus. You are currently signed in with {email}
|
||||
</p>,
|
||||
<p key="2" className="pad-top">
|
||||
<Link button={true} className="btn btn--clicky" href="https://insomnia.rest/app/">
|
||||
Manage Account
|
||||
</Link>
|
||||
{" "}
|
||||
<PromptButton className="btn btn--clicky" onClick={this._handleClickLogout}>
|
||||
Log Out
|
||||
</PromptButton>
|
||||
</p>
|
||||
] : [
|
||||
<p key="0">
|
||||
<Link href="https://insomnia.rest/plus/">Insomnia Plus</Link> helps you <i>rest</i> easy
|
||||
by
|
||||
keeping your workspaces securely backed up and synced across all of your devices.
|
||||
</p>,
|
||||
<p key="1">
|
||||
Upgrade today to start enjoying
|
||||
{" "}
|
||||
<Link href="https://insomnia.rest/pricing/">all of the benefits</Link>, while also
|
||||
helping
|
||||
support my continuous effort of making Insomnia awesome! <i
|
||||
className="fa fa-smile-o txt-xl"/>
|
||||
</p>,
|
||||
<p key="2" className="pad-top text-center">
|
||||
<Link button={true}
|
||||
href="https://insomnia.rest/pricing"
|
||||
className="btn txt-lg btn--outlined">
|
||||
Create an Account
|
||||
</Link>
|
||||
{" "}
|
||||
<button className="btn" onClick={this._handleClickLogin}>Log In</button>
|
||||
</p>,
|
||||
<p key="3" className="text-center italic">
|
||||
<span className="txt-sm faint pad-top-sm">all plans include a free trial</span>
|
||||
</p>
|
||||
]}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
SettingsSync.propTypes = {
|
||||
loggedIn: PropTypes.bool.isRequired,
|
||||
firstName: PropTypes.string.isRequired,
|
||||
email: PropTypes.string.isRequired,
|
||||
handleExit: PropTypes.func.isRequired,
|
||||
handleUpdateSetting: PropTypes.func.isRequired,
|
||||
handleShowSignup: PropTypes.func.isRequired,
|
||||
handleCancelAccount: PropTypes.func.isRequired,
|
||||
handleLogout: PropTypes.func.isRequired,
|
||||
handleReset: PropTypes.func.isRequired,
|
||||
|
@ -10,8 +10,19 @@ import {trackEvent} from '../../../analytics/index';
|
||||
|
||||
|
||||
class SidebarRequestRow extends PureComponent {
|
||||
state = {
|
||||
dragDirection: 0,
|
||||
isEditing: false,
|
||||
};
|
||||
|
||||
state = {dragDirection: 0};
|
||||
_handleEditStart = () => {
|
||||
trackEvent('Request', 'Rename', 'In Place');
|
||||
this.setState({isEditing: true});
|
||||
};
|
||||
|
||||
_handleRequestUpdateName = name => {
|
||||
models.request.update(this.props.request, {name})
|
||||
};
|
||||
|
||||
_handleRequestCreateFromEmpty = () => {
|
||||
const parentId = this.props.requestGroup._id;
|
||||
@ -63,8 +74,7 @@ class SidebarRequestRow extends PureComponent {
|
||||
node = (
|
||||
<li className={classes}>
|
||||
<div className="sidebar__item" tabIndex={0}>
|
||||
<button className="sidebar__clickable"
|
||||
onClick={this._handleRequestCreateFromEmpty}>
|
||||
<button className="sidebar__clickable" onClick={this._handleRequestCreateFromEmpty}>
|
||||
<em>click to add first request...</em>
|
||||
</button>
|
||||
</div>
|
||||
@ -77,14 +87,11 @@ class SidebarRequestRow extends PureComponent {
|
||||
<button className="wide" onClick={this._handleRequestActivate}>
|
||||
<div className="sidebar__clickable">
|
||||
<MethodTag method={request.method}/>
|
||||
<Editable
|
||||
value={request.name}
|
||||
onEditStart={() => trackEvent('Request', 'Rename', 'In Place')}
|
||||
onSubmit={name => models.request.update(request, {name})}
|
||||
/>
|
||||
<Editable value={request.name}
|
||||
onEditStart={this._handleEditStart}
|
||||
onSubmit={this._handleRequestUpdateName}/>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div className="sidebar__actions">
|
||||
<RequestActionsDropdown
|
||||
handleDuplicateRequest={handleDuplicateRequest}
|
||||
@ -94,13 +101,16 @@ class SidebarRequestRow extends PureComponent {
|
||||
requestGroup={requestGroup}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return connectDragSource(connectDropTarget(node));
|
||||
if (!this.state.isEditing) {
|
||||
return connectDragSource(connectDropTarget(node));
|
||||
} else {
|
||||
return connectDropTarget(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -19,11 +19,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.cookie-editor__filter {
|
||||
margin: @padding-md @padding-md @padding-sm;
|
||||
padding-bottom: @padding-md;
|
||||
}
|
||||
|
||||
.cookie-editor__editor {
|
||||
padding: 0 @padding-md @padding-md @padding-md;
|
||||
overflow-y: auto;
|
||||
|
@ -4,24 +4,33 @@
|
||||
.form-control {
|
||||
outline: none;
|
||||
border: 0;
|
||||
padding-top: @padding-sm;
|
||||
margin-bottom: @padding-sm;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
button,
|
||||
input,
|
||||
textarea,
|
||||
input[type="number"],
|
||||
input[type="password"],
|
||||
input[type="email"],
|
||||
input[type="color"],
|
||||
input[type="date"],
|
||||
input[type="text"] {
|
||||
button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
margin-top: @padding-xs;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
height: 1rem;
|
||||
float: left;
|
||||
margin-top: @padding-xxs;
|
||||
margin-right: @padding-xs;
|
||||
}
|
||||
|
||||
& > button {
|
||||
width: auto;
|
||||
}
|
||||
@ -36,6 +45,14 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
&.form-control--thin {
|
||||
label {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
margin-bottom: @padding-xxs;
|
||||
}
|
||||
|
||||
&.form-control--padded,
|
||||
&.form-control--outlined,
|
||||
&.form-control--underlined {
|
||||
@ -77,6 +94,32 @@
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
flex-direction: row;
|
||||
|
||||
& > * {
|
||||
width: 100%;
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
& > p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
& > *:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
& > *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
text-align: center;
|
||||
padding: 0 (@padding-md * 1.5);
|
||||
|
@ -245,6 +245,7 @@
|
||||
|
||||
.editable {
|
||||
line-height: @line-height-xs;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -279,6 +280,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
|
||||
& > *:last-child {
|
||||
white-space: nowrap;
|
||||
|
@ -71,6 +71,10 @@
|
||||
color: @surprise !important;
|
||||
}
|
||||
|
||||
.boring {
|
||||
color: @font-super-light-bg !important;
|
||||
}
|
||||
|
||||
.bg-success,
|
||||
.bg-http-method-POST {
|
||||
background: @success !important;
|
||||
|
@ -1,7 +1,7 @@
|
||||
/* Padding */
|
||||
@padding-md: 1.26rem;
|
||||
@padding-sm: 0.63rem;
|
||||
@padding-xs: 0.44rem;
|
||||
@padding-xs: 0.42rem;
|
||||
@padding-xxs: 0.24rem;
|
||||
@padding-lg: 2.5rem;
|
||||
@padding-xl: 5rem;
|
||||
|
@ -63,6 +63,10 @@ p {
|
||||
line-height: 1.7em;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
label.label--small,
|
||||
table th {
|
||||
color: @hl;
|
||||
@ -110,18 +114,19 @@ code, pre, .monospace {
|
||||
.notice {
|
||||
text-align: center;
|
||||
color: @font-light-bg !important;
|
||||
padding: @padding-sm;
|
||||
padding: @padding-sm * 1.5;
|
||||
border: 1px solid;
|
||||
border-radius: @radius-md;
|
||||
}
|
||||
|
||||
.notice.warning {
|
||||
background: fadeout(@warning, 85);
|
||||
border: 1px solid @warning;
|
||||
background: fadeout(@warning, 90);
|
||||
border-color: @warning;
|
||||
}
|
||||
|
||||
.notice.info {
|
||||
background: fadeout(@info, 85);
|
||||
border: 1px solid @info;
|
||||
background: fadeout(@info, 90);
|
||||
border-color: @info;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
|
@ -108,7 +108,7 @@
|
||||
"mkdirp": "^0.5.1",
|
||||
"mousetrap": "^1.6.0",
|
||||
"nedb": "^1.8.0",
|
||||
"node-forge": "^0.6.43",
|
||||
"node-forge": "^0.6.46",
|
||||
"nunjucks": "^3.0.0",
|
||||
"raven": "^0.12.1",
|
||||
"react": "^15.3.1",
|
||||
@ -122,7 +122,6 @@
|
||||
"redux-thunk": "^2.0.1",
|
||||
"request": "^2.74.0",
|
||||
"reselect": "^2.5.4",
|
||||
"sjcl": "^1.0.6",
|
||||
"srp": "git@github.com:getinsomnia/srp-js.git#6ebd8c3acfbcf69645e4c6e6f8f6b55138ff22c3",
|
||||
"tough-cookie": "^2.3.1",
|
||||
"traverse": "^0.6.6",
|
||||
|
Loading…
Reference in New Issue
Block a user