From 3e760ca0b8c48bbaf426841536c1e55428b5cc42 Mon Sep 17 00:00:00 2001 From: Andrew Nicolaou Date: Wed, 15 May 2019 12:11:58 +0200 Subject: [PATCH] Styles Account and APIKeys components --- client/modules/User/components/APIKeyForm.jsx | 135 +++++++----------- client/modules/User/components/APIKeyList.jsx | 50 +++++++ client/modules/User/pages/AccountView.jsx | 10 +- client/styles/components/_account.scss | 4 + client/styles/components/_api-key.scss | 102 +++++++++++++ client/styles/components/_form-container.scss | 31 ++-- client/styles/components/_forms.scss | 36 ++--- client/styles/main.scss | 2 + 8 files changed, 244 insertions(+), 126 deletions(-) create mode 100644 client/modules/User/components/APIKeyList.jsx create mode 100644 client/styles/components/_account.scss create mode 100644 client/styles/components/_api-key.scss diff --git a/client/modules/User/components/APIKeyForm.jsx b/client/modules/User/components/APIKeyForm.jsx index a0da81b6..17ec61e7 100644 --- a/client/modules/User/components/APIKeyForm.jsx +++ b/client/modules/User/components/APIKeyForm.jsx @@ -1,67 +1,19 @@ import PropTypes from 'prop-types'; import React from 'react'; import InlineSVG from 'react-inlinesvg'; -import format from 'date-fns/format'; -import distanceInWordsToNow from 'date-fns/distance_in_words_to_now'; -import orderBy from 'lodash/orderBy'; -const trashCan = require('../../../images/trash-can.svg'); +import CopyableInput from '../../IDE/components/CopyableInput'; +import APIKeyList from './APIKeyList'; -function NewTokenDisplay({ token }) { - function handleCopyClick() { - navigator.clipboard.writeText(token); - } +const plusIcon = require('../../../images/plus-icon.svg'); - return ( - -

Make sure to copy your new personal access token now. You won’t be able to see it again!

-

- -
- ); -} - -function TokenMetadataList({ tokens, onRemove }) { - return ( - - - - - - - - - - - {orderBy(tokens, ['createdAt'], ['desc']).map((v) => { - const keyRow = ( - - - - - - - ); - - const newKeyValue = v.token && ( - - - - ); - - return [keyRow, newKeyValue]; - })} - -
NameCreated onLast usedActions
{v.label}{format(new Date(v.createdAt), 'MMM D, YYYY h:mm A')}{distanceInWordsToNow(new Date(v.lastUsedAt), { addSuffix: true })} - -
- -
- ); -} +export const APIKeyPropType = PropTypes.shape({ + id: PropTypes.object.isRequired, + label: PropTypes.string.isRequired, + createdAt: PropTypes.object.isRequired, + lastUsedAt: PropTypes.object.isRequired, +}); class APIKeyForm extends React.Component { constructor(props) { @@ -99,45 +51,64 @@ class APIKeyForm extends React.Component { if (hasApiKeys) { return ( - + ); } - return

You have no API keys

; + return

You have no exsiting tokens.

; } render() { + const keyWithToken = this.props.apiKeys.find(k => !!k.token); + return ( -
-
- - { this.setState({ keyLabel: event.target.value }); }} - /> - -
- {this.renderApiKeys()} +
+

Personal Access Tokens act like your password to allow automated scripts to access the Editor API. Create a token for each script that needs access.

+ +
+

Create new token

+
+ + { this.setState({ keyLabel: event.target.value }); }} + placeholder="What is this token for? e.g. Example import script" + type="text" + value={this.state.keyLabel} + /> + +
+ + { + keyWithToken && ( +
+

Your new access token

+

Make sure to copy your new personal access token now. You won’t be able to see it again!

+ +
+ ) + } +
+ +
+

Existing tokens

+ {this.renderApiKeys()} +
); } } APIKeyForm.propTypes = { + apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired, createApiKey: PropTypes.func.isRequired, removeApiKey: PropTypes.func.isRequired, - apiKeys: PropTypes.arrayOf(PropTypes.shape({ - id: PropTypes.object.isRequired, - label: PropTypes.string.isRequired, - createdAt: PropTypes.object.isRequired, - lastUsedAt: PropTypes.object.isRequired, - })).isRequired }; export default APIKeyForm; diff --git a/client/modules/User/components/APIKeyList.jsx b/client/modules/User/components/APIKeyList.jsx new file mode 100644 index 00000000..b3df65dd --- /dev/null +++ b/client/modules/User/components/APIKeyList.jsx @@ -0,0 +1,50 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import InlineSVG from 'react-inlinesvg'; +import format from 'date-fns/format'; +import distanceInWordsToNow from 'date-fns/distance_in_words_to_now'; +import orderBy from 'lodash/orderBy'; + +import { APIKeyPropType } from './APIKeyForm'; + +const trashCan = require('../../../images/trash-can.svg'); + +function APIKeyList({ apiKeys, onRemove }) { + return ( + + + + + + + + + + + {orderBy(apiKeys, ['createdAt'], ['desc']).map((key) => { + const hasNewToken = !!key.token; + + return ( + + + + + + + ); + })} + +
NameCreated onLast usedActions
{key.label}{format(new Date(key.createdAt), 'MMM D, YYYY h:mm A')}{distanceInWordsToNow(new Date(key.lastUsedAt), { addSuffix: true })} + +
+ ); +} + +APIKeyList.propTypes = { + apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired, + onRemove: PropTypes.func.isRequired, +}; + +export default APIKeyList; diff --git a/client/modules/User/pages/AccountView.jsx b/client/modules/User/pages/AccountView.jsx index 9cf18eb8..0da5a37d 100644 --- a/client/modules/User/pages/AccountView.jsx +++ b/client/modules/User/pages/AccountView.jsx @@ -16,7 +16,6 @@ import APIKeyForm from '../components/APIKeyForm'; const exitUrl = require('../../../images/exit.svg'); const logoUrl = require('../../../images/p5js-logo.svg'); - class AccountView extends React.Component { constructor(props) { super(props); @@ -38,7 +37,7 @@ class AccountView extends React.Component { render() { return ( -
+
p5.js Web Editor | Account @@ -52,7 +51,7 @@ class AccountView extends React.Component {

My Account

- +

Account

@@ -61,8 +60,9 @@ class AccountView extends React.Component { -

Or

- +

Social Login

+

Link this account with your GitHub account to allow login from both.

+
diff --git a/client/styles/components/_account.scss b/client/styles/components/_account.scss new file mode 100644 index 00000000..2b8b601b --- /dev/null +++ b/client/styles/components/_account.scss @@ -0,0 +1,4 @@ +.account__tabs { + width: 500px; + padding-top: #{20 / $base-font-size}rem; +} diff --git a/client/styles/components/_api-key.scss b/client/styles/components/_api-key.scss new file mode 100644 index 00000000..16d2bbfe --- /dev/null +++ b/client/styles/components/_api-key.scss @@ -0,0 +1,102 @@ +.api-key-form__summary { + padding-top: #{25 / $base-font-size}rem; + @include themify() { + color: getThemifyVariable('heading-text-color'); + } +} + +.api-key-form__section { + padding-bottom: #{15 / $base-font-size}rem; +} + +.api-key-form__title { + padding: #{15 / $base-font-size}rem 0; + font-size: #{21 / $base-font-size}rem; + font-weight: bold; + @include themify() { + color: getThemifyVariable('heading-text-color'); + } +} + +.api-key-form__create-button { + display: flex; + justify-content: center; +} + +.api-key-form__create-button .isvg { + padding-right: 10px; +} + +.api-key-list { + display: block; + max-width: 900px; + border-collapse: collapse; + table-layout: fixed; + + thead tr th { + width: 30%; + } + + thead tr th:last-child { + width: 10%; + } + + th { + padding: #{5 / $base-font-size}rem; + } + + td { + padding: #{15 / $base-font-size}rem #{5 / $base-font-size}rem; + } + + tbody tr:nth-child(odd) { + background-color: #f2f2f2; + } +} + +.api-key-list__action { + text-align: center; +} + +.api-key-list__delete-button { + width:#{20 / $base-font-size}rem; + height:#{20 / $base-font-size}rem; + + text-align: center; + + @include themify() { + background-color: transparent; + border: none; + cursor: pointer; + padding: 0; + position: initial; + left: 0; + top: 0; + & g { + opacity: 1; + fill: getThemifyVariable('icon-color'); + } + } +} + +.api-key-list__delete-button:hover { + @include themify() { + & g { + opacity: 1; + fill: getThemifyVariable('icon-hover-color'); + } + } +} + +.api-key-form__new-token__title { + margin-bottom: #{10 / $base-font-size}rem; + font-size: #{18 / $base-font-size}rem; + font-weight: bold; + @include themify() { + color: getThemifyVariable('heading-text-color'); + } +} + +.api-key-form__new-token__info { + padding: #{10 / $base-font-size}rem 0; +} diff --git a/client/styles/components/_form-container.scss b/client/styles/components/_form-container.scss index 743aad21..3514d4f6 100644 --- a/client/styles/components/_form-container.scss +++ b/client/styles/components/_form-container.scss @@ -6,6 +6,10 @@ align-items: center; } +.form-container--align-left { + text-align: left; +} + .form-container--align-top { height: unset; } @@ -25,11 +29,21 @@ align-items: center; } +.form-container--align-left .form-container__content { + align-items: left; +} + .form-container__title { font-weight: normal; color: $form-title-color; } +.form-container__context { + @include themify() { + color: getThemifyVariable('secondary-text-color') + } +} + .form-container__divider { padding: #{20 / $base-font-size}rem 0; } @@ -41,20 +55,3 @@ .form-container__exit-button { @extend %none-themify-icon-with-hover; } - -.form__table { - display: block; - max-width: 900px; - border-collapse: collapse; - & td { - max-width: 300px; - border: 1px solid #ddd; - padding: 0 10px 0 10px; - } - & tr:nth-child(even) { - background-color: #f2f2f2; - } - & tr.new-key { - background-color: #f2f2f2; - } -} diff --git a/client/styles/components/_forms.scss b/client/styles/components/_forms.scss index 8fe66361..94ba4501 100644 --- a/client/styles/components/_forms.scss +++ b/client/styles/components/_forms.scss @@ -9,6 +9,11 @@ } } +.form--inline { + display: flex; + align-items: center; +} + .form__cancel-button { margin-top: #{10 / $base-font-size}rem; font-size: #{12 / $base-font-size}rem; @@ -20,6 +25,11 @@ color: $form-navigation-options-color; } +.form__legend{ + font-size: #{21 / $base-font-size}rem; + font-weight: bold; +} + .form__label { color: $secondary-form-title-color; font-size: #{12 / $base-font-size}rem; @@ -28,6 +38,10 @@ display: block; } +.form__label--hidden { + @extend %hidden-element; +} + .form__input { width: #{360 / $base-font-size}rem; height: #{40 / $base-font-size}rem; @@ -51,25 +65,3 @@ .form input[type="submit"]:disabled { cursor: not-allowed; } - -.form__table-button-remove { - @extend %forms-button; - margin: 1rem 0 1rem 0; - @include themify() { - color: getThemifyVariable('console-error-background-color'); - border-color: getThemifyVariable('console-error-background-color'); - &:enabled:hover { - border-color: getThemifyVariable('console-error-background-color'); - background-color: getThemifyVariable('console-error-background-color'); - } - &:enabled:active { - border-color: getThemifyVariable('console-error-background-color'); - background-color: getThemifyVariable('console-error-background-color'); - } - } -} - -.form__table-button-copy{ - @extend %forms-button; - margin: 1rem 0 1rem 0; -} diff --git a/client/styles/main.scss b/client/styles/main.scss index 3d9bf977..cff7ff67 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -14,6 +14,8 @@ @import 'components/p5-light-codemirror-theme'; @import 'components/p5-dark-codemirror-theme'; @import 'components/p5-contrast-codemirror-theme'; +@import 'components/account'; +@import 'components/api-key'; @import 'components/editor'; @import 'components/nav'; @import 'components/preview-nav';