From 59cf8ed64f34738911c95193735bd6b8ec8ddad8 Mon Sep 17 00:00:00 2001 From: Ann Catton Date: Sun, 17 Jan 2021 14:45:41 -0500 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8=20Add=20user=20page=20ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/Button.tsx | 114 +++++++ components/Footer.tsx | 13 +- components/Link.tsx | 15 + components/PageLayout.tsx | 10 +- components/UserDropdown.tsx | 2 +- components/pages/user/index.tsx | 402 +++++++++++++++++++++++ components/theme/colors.ts | 2 +- components/theme/icons/checkmark.tsx | 28 ++ components/theme/icons/index.tsx | 6 + components/theme/icons/overture_user.tsx | 96 ++++++ components/theme/icons/spinner.tsx | 31 ++ package-lock.json | 32 +- package.json | 4 +- pages/profile/index.tsx | 7 - pages/user/index.tsx | 13 + static/checkmark.svg | 6 + static/overture-user.svg | 48 +++ 17 files changed, 797 insertions(+), 32 deletions(-) create mode 100644 components/Button.tsx create mode 100644 components/Link.tsx create mode 100644 components/pages/user/index.tsx create mode 100644 components/theme/icons/checkmark.tsx create mode 100644 components/theme/icons/overture_user.tsx create mode 100644 components/theme/icons/spinner.tsx delete mode 100644 pages/profile/index.tsx create mode 100644 pages/user/index.tsx create mode 100644 static/checkmark.svg create mode 100644 static/overture-user.svg diff --git a/components/Button.tsx b/components/Button.tsx new file mode 100644 index 00000000..9c5d73b9 --- /dev/null +++ b/components/Button.tsx @@ -0,0 +1,114 @@ +import React, { ReactNode, ReactNodeArray } from 'react'; +import { css } from '@emotion/core'; +import styled from '@emotion/styled'; + +import defaultTheme from './theme'; +import { useTheme } from 'emotion-theming'; +import theme from './theme'; +import { Spinner } from './theme/icons'; + +const ButtonElement = styled('button')` + color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.white)}; + background-color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.accent)}; + ${({ theme }) => css(theme.typography.subheading2)}; + line-height: 24px; + border-radius: 5px; + border: 0px; + padding: 6px 15px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + position: relative; + &:disabled { + background-color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.grey_4)}; + cursor: not-allowed; + color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.white)}; + border: 1px solid ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.grey_4)}; + } + &:hover { + } +`; + +const Button = React.forwardRef< + HTMLButtonElement, + { + children?: ReactNode | ReactNodeArray; + disabled?: boolean; + onClick?: ( + e: React.SyntheticEvent, + ) => any | ((e: React.SyntheticEvent) => Promise); + /** + * Use with async onClick handlers to set loading indicator + */ + isAsync?: boolean; + + /** + * DOM pass through + */ + className?: string; + /** + * DOM pass through + */ + id?: string; + isLoading?: boolean; + color?: string; + } +>( + ( + { + children, + onClick = (e) => {}, + disabled = false, + isAsync = false, + className, + id, + isLoading: controlledLoadingState, + color, + }, + ref = React.createRef(), + ) => { + const [isLoading, setLoading] = React.useState(false); + + /** + * controlledLoadingState will allows consumer to control the loading state. + * Else, that is set by the component internally + */ + const shouldShowLoading = !!controlledLoadingState || (isLoading && isAsync); + + const onClickFn = async (event: any) => { + setLoading(true); + await onClick(event); + setLoading(false); + }; + return ( + + + {children} + + css` + position: absolute; + visibility: ${shouldShowLoading ? 'visible' : 'hidden'}; + bottom: 1px; + fill: ${color || theme.colors.white}; + `} + > + + + + ); + }, +); + +export default Button; diff --git a/components/Footer.tsx b/components/Footer.tsx index 49360de7..b5f626fc 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -1,15 +1,10 @@ import React from 'react'; import { css } from '@emotion/core'; -import styled from '@emotion/styled'; import defaultTheme from './theme'; import { OvertureLogoWithText } from './theme/icons'; -const StyledLink = styled('a')` - color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.secondary_accessible)}; - ${({ theme }) => css(theme.typography.subheading2)}; - line-height: 24px; -`; +import StyledLink from './Link'; const Footer = () => { return ( @@ -27,7 +22,8 @@ const Footer = () => { `} > css` + ${theme.typography.subheading2}; padding-right: 13px; `} // TODO: broken link @@ -38,7 +34,8 @@ const Footer = () => { | css` + ${theme.typography.subheading2}; padding-left: 13px; padding-right: 5px; `} diff --git a/components/Link.tsx b/components/Link.tsx new file mode 100644 index 00000000..eaa107fc --- /dev/null +++ b/components/Link.tsx @@ -0,0 +1,15 @@ +import styled from '@emotion/styled'; +import { css } from '@emotion/core'; + +import defaultTheme from './theme'; + +const StyledLink = styled('a')` + color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.secondary_accessible)}; + ${({ theme }: { theme: typeof defaultTheme }) => css(theme.typography.regular)}; + line-height: 24px; + &:hover { + color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.accent)}; + } +`; + +export default StyledLink; diff --git a/components/PageLayout.tsx b/components/PageLayout.tsx index d9130366..c8393c3a 100644 --- a/components/PageLayout.tsx +++ b/components/PageLayout.tsx @@ -5,11 +5,17 @@ import defaultTheme from './theme'; import NavBar from './NavBar'; import Footer from './Footer'; -const PageLayout = ({ children }: { children: React.ReactNode }) => { +const PageLayout = ({ + children, + backgroundColor, +}: { + children: React.ReactNode; + backgroundColor: string; +}) => { return (
css` - background-color: ${theme.colors.grey_2}; + background-color: ${backgroundColor || theme.colors.grey_2}; display: grid; grid-template-rows: 50px 1fr; min-height: 100vh; diff --git a/components/UserDropdown.tsx b/components/UserDropdown.tsx index f561c0a5..4f62d417 100644 --- a/components/UserDropdown.tsx +++ b/components/UserDropdown.tsx @@ -130,7 +130,7 @@ const UserDropdown = () => { `} >
  • - Profile & Token + Profile & Token
  • {/* TODO: implement logout */}
  • diff --git a/components/pages/user/index.tsx b/components/pages/user/index.tsx new file mode 100644 index 00000000..4f93ecd4 --- /dev/null +++ b/components/pages/user/index.tsx @@ -0,0 +1,402 @@ +import { css } from '@emotion/core'; +import React from 'react'; +import { capitalize, isEmpty, sample, set } from 'lodash'; +import { useTheme } from 'emotion-theming'; + +import PageLayout from '../../PageLayout'; +import { + GoogleLogo, + FacebookLogo, + GitHubLogo, + OrcidLogo, + LinkedInLogo, + OvertureUser, + Checkmark, +} from '../../theme/icons'; + +import defaultTheme from '../../theme'; +import theme from '../../theme'; +import Button from '../../Button'; +import StyledLink from '../../Link'; + +type ProviderType = 'GOOGLE' | 'FACEBOOK' | 'GITHUB' | 'LINKEDIN' | 'ORCID'; +type UserType = 'ADMIN' | 'USER'; +type UserStatus = 'APPROVED' | 'PENDING' | 'DISABLED' | 'REJECTED'; +type Language = 'English' | 'French' | 'Spanish'; + +interface ApiToken { + expiryDate: string; + isRevoked: boolean; + issueDate: string; + name: string; + scope: string[]; +} + +interface User { + id: string; + email: string; + type: UserType; + status: UserStatus; + firstName: string; + lastName: string; + createdAt: string; + lastLogin: string; + preferredLanguage?: Language; + providerType: ProviderType; + providerSubjectId: string; +} + +const providerIcons: { [k in ProviderType]: React.ElementType } = { + GOOGLE: GoogleLogo, + FACEBOOK: FacebookLogo, + GITHUB: GitHubLogo, + LINKEDIN: LinkedInLogo, + ORCID: OrcidLogo, +}; + +// for testing ui until data hookup is ready +const sampleToken: ApiToken = { + name: 'f4b7ca22-aaaa-4455-86ed-ec2fed727e72', + scope: ['Test-Policy.WRITE'], + expiryDate: '2021-02-14T15:58:37.151+0000', + issueDate: '2021-01-15T15:58:37.151+0000', + isRevoked: false, +}; +const sampleUser: User = { + id: 'aaaabbbb-cccc-dddd-eeee-ffffgggg', + email: 'user@example.com', + firstName: 'User', + lastName: 'Example', + providerType: 'GOOGLE', + providerSubjectId: 'prov-subj-id-0123', + status: 'APPROVED', + type: 'USER', + createdAt: '2020-02-14T15:58:37.151+0000', + lastLogin: '2020-02-14T15:58:37.151+0000', +}; + +const AuthenticatedBadge = ({ provider }: { provider: ProviderType }) => { + const IconComponent = providerIcons[provider]; + const theme: typeof defaultTheme = useTheme(); + return ( +
    + css` + width: 235px; + max-height: 30px; + border: 1px solid ${theme.colors.grey_5}; + border-radius: 5px; + background-color: ${theme.colors.white}; + ${theme.typography.data}; + display: flex; + justify-content: space-between; + align-items: center; + padding: 2px 7px 0; + ` + } + > + + css` + border-right: 1px solid ${theme.colors.grey_5}; + padding: 0 10px 0 0; + ` + } + > + + + css` + ${theme.typography.data}; + color: ${theme.colors.accent_dark}; + `} + > + Authenticated with {capitalize(provider)} + + +
    + ); +}; + +const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { + const [isCopyingToken, setIsCopyingToken] = React.useState(false); + const [copySuccess, setCopySuccess] = React.useState(false); + + const sleep = (time: number = 2000) => + new Promise((resolve) => { + setTimeout(() => { + resolve(''); + }, time); + }); + + const copyApiToken = (text: string) => { + setIsCopyingToken(true); + console.log('copying token'); + navigator.clipboard + .writeText(text) + .then(async () => { + await setIsCopyingToken(false); + await setCopySuccess(true); + await sleep(); + setCopySuccess(false); + }) + .catch((err) => console.warn('Failed to copy token')); + }; + + const parseExpiry = (exp: string) => { + const expFromTodayMs = Date.parse(exp) - Date.now(); + return expFromTodayMs || 0; + }; + + const getDayValue = (exp: number) => { + // round or floor? + const days = Math.round(exp / 1000 / 60 / 60 / 24); + return `Expires in: ${days} days`; + }; + + const parsedExpiry = apiToken ? parseExpiry(apiToken?.expiryDate) : 0; + const tokenIsExpired = parsedExpiry <= 0; + + return ( +
    +

    + css` + ${theme.typography.regular}; + font-size: 24px; + line-height: 40px; + color: ${theme.colors.accent_dark}; + ` + } + > + API Token +

    +
      + css` + ${theme.typography.subheading}; + font-weight: normal; + color: ${theme.colors.accent_dark}; + margin-bottom: 2rem; + ` + } + > +
    1. Your API token is used to download controlled access data.
    2. +
    3. + Your API token is associated with your user credentials and should NEVER{' '} + be shared with anyone. +
    4. +
    5. When you generate a new token, all previous tokens become invalid.
    6. +
    7. Expired and revoked tokens also become invalid.
    8. +
    +
    + + +
    +
    +
    + css` + border: 1px solid ${theme.colors.grey_5}; + border-radius: 5px 0px 0px 5px; + border-right: 0px; + color: ${isEmpty(apiToken) ? theme.colors.grey_6 : theme.colors.black}; + width: 100%; + display: flex; + align-items: center; + padding-left: 5px; + ` + } + > + {!isEmpty(apiToken) && ( +
    + css` + color: ${theme.colors.white}; + border-radius: 6px; + padding: 3px 8px; + margin-right: 5px; + display: flex; + justify-content: center; + align-items: center; + ${theme.typography.label} + ${tokenIsExpired + ? `background-color: ${theme.colors.error_dark}` + : `background-color: ${theme.colors.grey_6}; + min-width: 100px;`} + ` + } + > + {tokenIsExpired ? 'Expired' : getDayValue(parsedExpiry)} +
    + )} + + {apiToken?.name || 'You have no API token...'} + +
    + +
    + + + css` + ${theme.typography.subheading}; + font-weight: normal; + color: ${theme.colors.accent_dark}; + ` + } + > + For more information, please read the{' '} + instructions on how to download data. + +
    + ); +}; + +const User = ({ provider = 'GOOGLE' }: { provider: ProviderType }) => { + return ( + +
    + css` + margin: 1rem 20rem; + display: flex; + flex-direction: column; + ` + } + > +
    +
    + +
    +

    + css` + ${theme.typography.regular}; + font-size: 30px; + line-height: 36px; + color: ${theme.colors.accent_dark}; + margin-bottom: 0.5rem; + margin-top: 0.1rem; + ` + } + > + {`${sampleUser.firstName} ${sampleUser.lastName}`} +

    +
    + css` + ${theme.typography.subheading}; + color: ${theme.colors.accent_dark}; + font-weight: normal; + padding-left: 0.2rem; + ` + } + > + {sampleUser.email || ''} +
    +
    +
    + +
    + +
    +
    + ); +}; + +export default User; diff --git a/components/theme/colors.ts b/components/theme/colors.ts index 06aa6b6e..bfdf37ad 100644 --- a/components/theme/colors.ts +++ b/components/theme/colors.ts @@ -22,7 +22,7 @@ const primary = { const accent = { accent: '#04518c', accent_light: '#4f85ae', - accent_dark: '#151c3d', + accent_dark: '#003055', accent_1: '#e5edf3', }; diff --git a/components/theme/icons/checkmark.tsx b/components/theme/icons/checkmark.tsx new file mode 100644 index 00000000..ccc513cb --- /dev/null +++ b/components/theme/icons/checkmark.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/core'; + +import { IconProps } from './types'; + +const Checkmark = ({ fill, height, width, style }: IconProps) => { + return ( + + + + + + + ); +}; + +export default Checkmark; diff --git a/components/theme/icons/index.tsx b/components/theme/icons/index.tsx index 7bb31847..7cf6348d 100644 --- a/components/theme/icons/index.tsx +++ b/components/theme/icons/index.tsx @@ -8,6 +8,9 @@ import Avatar from './avatar'; import ChevronDown from './chevron_down'; import OvertureLogo from './overture_logo'; import OvertureLogoWithText from './overture_logo_with_text'; +import OvertureUser from './overture_user'; +import Checkmark from './checkmark'; +import Spinner from './spinner'; export { GoogleLogo, @@ -20,4 +23,7 @@ export { ChevronDown, OvertureLogo, OvertureLogoWithText, + OvertureUser, + Checkmark, + Spinner, }; diff --git a/components/theme/icons/overture_user.tsx b/components/theme/icons/overture_user.tsx new file mode 100644 index 00000000..257cbcd2 --- /dev/null +++ b/components/theme/icons/overture_user.tsx @@ -0,0 +1,96 @@ +import { css } from '@emotion/core'; + +import { IconProps } from './types'; + +const OvertureUser = ({ height, width, style }: IconProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default OvertureUser; diff --git a/components/theme/icons/spinner.tsx b/components/theme/icons/spinner.tsx new file mode 100644 index 00000000..c2620ef6 --- /dev/null +++ b/components/theme/icons/spinner.tsx @@ -0,0 +1,31 @@ +import { css, keyframes } from '@emotion/core'; +import { IconProps } from './types'; + +// Animation +const spin = keyframes` + 100% { + transform: rotate(360deg); + } +`; + +const Spinner = ({ fill, height, width }: IconProps) => { + return ( + + + + ); +}; + +export default Spinner; diff --git a/package-lock.json b/package-lock.json index b3b8bce7..c27c4d91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "dms-ui", - "version": "0.2.0", + "version": "0.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -162,6 +162,13 @@ "morgan": "^1.9.0", "utf8": "^3.0.0", "winston": "^2.4.0" + }, + "dependencies": { + "date-fns": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", + "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" + } } }, "@babel/code-frame": { @@ -2448,6 +2455,12 @@ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" }, + "@types/lodash": { + "version": "4.14.167", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.167.tgz", + "integrity": "sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw==", + "dev": true + }, "@types/node": { "version": "14.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.10.1.tgz", @@ -3174,9 +3187,9 @@ "dev": true }, "axios": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz", - "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { "follow-redirects": "^1.10.0" } @@ -4604,11 +4617,6 @@ } } }, - "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -5615,9 +5623,9 @@ } }, "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" }, "for-in": { "version": "1.0.2", diff --git a/package.json b/package.json index eb65376a..33eb2391 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,9 @@ "@emotion/core": "^10.0.35", "@emotion/styled": "^10.0.27", "@zeit/next-css": "^1.0.1", - "axios": "^0.20.0", + "axios": "^0.21.1", "emotion-theming": "^10.0.27", + "lodash": "^4.17.20", "next": "^9.5.4", "query-string": "^6.13.2", "react": "16.13.1", @@ -24,6 +25,7 @@ "url-join": "^4.0.1" }, "devDependencies": { + "@types/lodash": "^4.14.167", "@types/node": "^14.10.1", "@types/react": "^16.9.49", "jest": "^26.5.3", diff --git a/pages/profile/index.tsx b/pages/profile/index.tsx deleted file mode 100644 index 116c7566..00000000 --- a/pages/profile/index.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -const ProfilePage = () => { - return
    This is the Profile Page
    ; -}; - -export default ProfilePage; diff --git a/pages/user/index.tsx b/pages/user/index.tsx new file mode 100644 index 00000000..efcacebd --- /dev/null +++ b/pages/user/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import User from '../../components/pages/user'; +import { createPage } from '../../global/utils/pages'; + +const UserPage = createPage({ + getInitialProps: async ({ egoJwt }) => { + return { egoJwt }; + }, +})(() => { + return ; +}); + +export default UserPage; diff --git a/static/checkmark.svg b/static/checkmark.svg new file mode 100644 index 00000000..5774eb4d --- /dev/null +++ b/static/checkmark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/static/overture-user.svg b/static/overture-user.svg new file mode 100644 index 00000000..532e3dd2 --- /dev/null +++ b/static/overture-user.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 147399a5251b59cc97e0bf20f14d2f0d5fbee23c Mon Sep 17 00:00:00 2001 From: Ann Catton Date: Sun, 17 Jan 2021 15:32:05 -0500 Subject: [PATCH 2/5] add copy tooltip --- components/pages/user/index.tsx | 103 +++++++++++++++++++++++--------- package.json | 1 + 2 files changed, 77 insertions(+), 27 deletions(-) diff --git a/components/pages/user/index.tsx b/components/pages/user/index.tsx index 4f93ecd4..4829e684 100644 --- a/components/pages/user/index.tsx +++ b/components/pages/user/index.tsx @@ -1,4 +1,4 @@ -import { css } from '@emotion/core'; +import { css, Global } from '@emotion/core'; import React from 'react'; import { capitalize, isEmpty, sample, set } from 'lodash'; import { useTheme } from 'emotion-theming'; @@ -18,6 +18,8 @@ import defaultTheme from '../../theme'; import theme from '../../theme'; import Button from '../../Button'; import StyledLink from '../../Link'; +import { Tooltip } from 'react-tippy'; +import styled from '@emotion/styled'; type ProviderType = 'GOOGLE' | 'FACEBOOK' | 'GITHUB' | 'LINKEDIN' | 'ORCID'; type UserType = 'ADMIN' | 'USER'; @@ -157,6 +159,31 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { const parsedExpiry = apiToken ? parseExpiry(apiToken?.expiryDate) : 0; const tokenIsExpired = parsedExpiry <= 0; + const TooltipContainer = styled('div')` + ${css(theme.typography.label as any)} + background: ${theme.colors.grey_6}; + border-radius: 2px; + padding: 2px 4px; + color: white; + font-weight: normal; + margin-bottom: 10%; + &:before { + content: ''; + display: block; + position: absolute; + width: 0; + height: 0; + border: 5px solid transparent; + pointer-events: none; + right: 50%; + top: 79%; + border-top-color: ${theme.colors.grey_6}; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + margin-right: -5px; + } + `; + return (
    { {apiToken?.name || 'You have no API token...'}
    - + + + + + Copy + + + +
  • Date: Mon, 18 Jan 2021 12:57:19 -0500 Subject: [PATCH 3/5] refactor, style changes --- components/Button.tsx | 29 +++++++---------------------- components/pages/user/index.tsx | 24 +++++++++++++----------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/components/Button.tsx b/components/Button.tsx index 9c5d73b9..df25bf51 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -3,8 +3,6 @@ import { css } from '@emotion/core'; import styled from '@emotion/styled'; import defaultTheme from './theme'; -import { useTheme } from 'emotion-theming'; -import theme from './theme'; import { Spinner } from './theme/icons'; const ButtonElement = styled('button')` @@ -13,21 +11,24 @@ const ButtonElement = styled('button')` ${({ theme }) => css(theme.typography.subheading2)}; line-height: 24px; border-radius: 5px; - border: 0px; + border: 1px solid ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.accent)}; padding: 6px 15px; display: flex; justify-content: center; align-items: center; cursor: pointer; position: relative; - &:disabled { + &:hover { + background-color: ${({ theme }: { theme: typeof defaultTheme }) => + css(theme.colors.accent_dark)}; + } + &:disabled, + &:disabled:hover { background-color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.grey_4)}; cursor: not-allowed; color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.white)}; border: 1px solid ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.grey_4)}; } - &:hover { - } `; const Button = React.forwardRef< @@ -38,21 +39,9 @@ const Button = React.forwardRef< onClick?: ( e: React.SyntheticEvent, ) => any | ((e: React.SyntheticEvent) => Promise); - /** - * Use with async onClick handlers to set loading indicator - */ isAsync?: boolean; - - /** - * DOM pass through - */ className?: string; - /** - * DOM pass through - */ - id?: string; isLoading?: boolean; - color?: string; } >( ( @@ -62,9 +51,7 @@ const Button = React.forwardRef< disabled = false, isAsync = false, className, - id, isLoading: controlledLoadingState, - color, }, ref = React.createRef(), ) => { @@ -87,7 +74,6 @@ const Button = React.forwardRef< onClick={isAsync ? onClickFn : onClick} disabled={disabled || shouldShowLoading} className={className} - // id={id} > diff --git a/components/pages/user/index.tsx b/components/pages/user/index.tsx index 4829e684..cd602cef 100644 --- a/components/pages/user/index.tsx +++ b/components/pages/user/index.tsx @@ -1,6 +1,6 @@ import { css, Global } from '@emotion/core'; import React from 'react'; -import { capitalize, isEmpty, sample, set } from 'lodash'; +import { capitalize, has, isEmpty, sample, set } from 'lodash'; import { useTheme } from 'emotion-theming'; import PageLayout from '../../PageLayout'; @@ -56,7 +56,8 @@ const providerIcons: { [k in ProviderType]: React.ElementType } = { ORCID: OrcidLogo, }; -// for testing ui until data hookup is ready +// for testing ui until auth hookup is ready +// user info will come from jwt const sampleToken: ApiToken = { name: 'f4b7ca22-aaaa-4455-86ed-ec2fed727e72', scope: ['Test-Policy.WRITE'], @@ -133,7 +134,6 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { const copyApiToken = (text: string) => { setIsCopyingToken(true); - console.log('copying token'); navigator.clipboard .writeText(text) .then(async () => { @@ -142,7 +142,7 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { await sleep(); setCopySuccess(false); }) - .catch((err) => console.warn('Failed to copy token')); + .catch((err) => console.warn('Failed to copy token!')); }; const parseExpiry = (exp: string) => { @@ -156,8 +156,8 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { return `Expires in: ${days} days`; }; - const parsedExpiry = apiToken ? parseExpiry(apiToken?.expiryDate) : 0; - const tokenIsExpired = parsedExpiry <= 0; + const parsedExpiry: number = apiToken ? parseExpiry(apiToken?.expiryDate) : 0; + const tokenIsExpired: boolean = has(apiToken, 'expiryDate') && parsedExpiry <= 0; const TooltipContainer = styled('div')` ${css(theme.typography.label as any)} @@ -242,6 +242,9 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { background-color: ${theme.colors.white}; color: ${theme.colors.accent_dark}; border: 1px solid ${theme.colors.grey_5}; + &:hover { + background-color: ${theme.colors.accent_1}; + } ` } > @@ -286,8 +289,7 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { ${theme.typography.label} ${tokenIsExpired ? `background-color: ${theme.colors.error_dark}` - : `background-color: ${theme.colors.grey_6}; - min-width: 100px;`} + : `background-color: ${theme.colors.grey_6};`} ` } > @@ -303,7 +305,7 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { font-weight: normal; padding-right: 5px; padding-left: 5px; - ${tokenIsExpired ? `opacity: 0.3` : ''} + ${tokenIsExpired ? 'opacity: 0.3' : ''} `} > {apiToken?.name || 'You have no API token...'} @@ -342,7 +344,7 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { css={css` position: absolute; top: 8px; - left: 22px; + left: 24px; visibility: ${copySuccess ? 'visible' : 'hidden'}; `} > @@ -376,7 +378,7 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { ); }; -const User = ({ provider = 'GOOGLE' }: { provider: ProviderType }) => { +const User = () => { return (
    Date: Mon, 18 Jan 2021 13:09:02 -0500 Subject: [PATCH 4/5] slight refactor --- components/pages/user/index.tsx | 50 ++++++++++++++++----------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/components/pages/user/index.tsx b/components/pages/user/index.tsx index cd602cef..b15c866d 100644 --- a/components/pages/user/index.tsx +++ b/components/pages/user/index.tsx @@ -121,6 +121,31 @@ const AuthenticatedBadge = ({ provider }: { provider: ProviderType }) => { ); }; +const TooltipContainer = styled('div')` + ${css(theme.typography.label as any)} + background: ${theme.colors.grey_6}; + border-radius: 2px; + padding: 2px 4px; + color: white; + font-weight: normal; + margin-bottom: 10%; + &:before { + content: ''; + display: block; + position: absolute; + width: 0; + height: 0; + border: 5px solid transparent; + pointer-events: none; + right: 50%; + top: 79%; + border-top-color: ${theme.colors.grey_6}; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + margin-right: -5px; + } +`; + const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { const [isCopyingToken, setIsCopyingToken] = React.useState(false); const [copySuccess, setCopySuccess] = React.useState(false); @@ -159,31 +184,6 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { const parsedExpiry: number = apiToken ? parseExpiry(apiToken?.expiryDate) : 0; const tokenIsExpired: boolean = has(apiToken, 'expiryDate') && parsedExpiry <= 0; - const TooltipContainer = styled('div')` - ${css(theme.typography.label as any)} - background: ${theme.colors.grey_6}; - border-radius: 2px; - padding: 2px 4px; - color: white; - font-weight: normal; - margin-bottom: 10%; - &:before { - content: ''; - display: block; - position: absolute; - width: 0; - height: 0; - border: 5px solid transparent; - pointer-events: none; - right: 50%; - top: 79%; - border-top-color: ${theme.colors.grey_6}; - border-right: 5px solid transparent; - border-left: 5px solid transparent; - margin-right: -5px; - } - `; - return (
    Date: Tue, 19 Jan 2021 17:08:02 -0500 Subject: [PATCH 5/5] pr feedback --- components/Button.tsx | 47 +++++++++++---------- components/Link.tsx | 14 +++--- components/PageLayout.tsx | 12 +----- components/UserDropdown.tsx | 12 +++--- components/pages/repository/PageContent.tsx | 8 ++-- components/pages/user/index.tsx | 11 +++-- 6 files changed, 53 insertions(+), 51 deletions(-) diff --git a/components/Button.tsx b/components/Button.tsx index df25bf51..19480185 100644 --- a/components/Button.tsx +++ b/components/Button.tsx @@ -6,29 +6,30 @@ import defaultTheme from './theme'; import { Spinner } from './theme/icons'; const ButtonElement = styled('button')` - color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.white)}; - background-color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.accent)}; - ${({ theme }) => css(theme.typography.subheading2)}; - line-height: 24px; - border-radius: 5px; - border: 1px solid ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.accent)}; - padding: 6px 15px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - position: relative; - &:hover { - background-color: ${({ theme }: { theme: typeof defaultTheme }) => - css(theme.colors.accent_dark)}; - } - &:disabled, - &:disabled:hover { - background-color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.grey_4)}; - cursor: not-allowed; - color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.white)}; - border: 1px solid ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.grey_4)}; - } + ${({ theme }: { theme: typeof defaultTheme }) => css` + color: ${theme.colors.white}; + background-color: ${theme.colors.accent}; + ${theme.typography.subheading2}; + line-height: 24px; + border-radius: 5px; + border: 1px solid ${theme.colors.accent}; + padding: 6px 15px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + position: relative; + &:hover { + background-color: ${theme.colors.accent_dark}; + } + &:disabled, + &:disabled:hover { + background-color: ${theme.colors.grey_4}; + cursor: not-allowed; + color: ${theme.colors.white}; + border: 1px solid ${theme.colors.grey_4}; + } + `} `; const Button = React.forwardRef< diff --git a/components/Link.tsx b/components/Link.tsx index eaa107fc..c68c53dd 100644 --- a/components/Link.tsx +++ b/components/Link.tsx @@ -4,12 +4,14 @@ import { css } from '@emotion/core'; import defaultTheme from './theme'; const StyledLink = styled('a')` - color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.secondary_accessible)}; - ${({ theme }: { theme: typeof defaultTheme }) => css(theme.typography.regular)}; - line-height: 24px; - &:hover { - color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.accent)}; - } + ${({ theme }: { theme: typeof defaultTheme }) => css` + color: ${theme.colors.secondary_accessible}; + ${theme.typography.regular}; + line-height: 24px; + &:hover { + color: ${theme.colors.accent}; + } + `} `; export default StyledLink; diff --git a/components/PageLayout.tsx b/components/PageLayout.tsx index c8393c3a..986afe49 100644 --- a/components/PageLayout.tsx +++ b/components/PageLayout.tsx @@ -1,21 +1,13 @@ import React from 'react'; import { css } from '@emotion/core'; -import defaultTheme from './theme'; import NavBar from './NavBar'; import Footer from './Footer'; -const PageLayout = ({ - children, - backgroundColor, -}: { - children: React.ReactNode; - backgroundColor: string; -}) => { +const PageLayout = ({ children }: { children: React.ReactNode }) => { return (
    css` - background-color: ${backgroundColor || theme.colors.grey_2}; + css={(theme) => css` display: grid; grid-template-rows: 50px 1fr; min-height: 100vh; diff --git a/components/UserDropdown.tsx b/components/UserDropdown.tsx index 4f62d417..0436f55f 100644 --- a/components/UserDropdown.tsx +++ b/components/UserDropdown.tsx @@ -33,21 +33,23 @@ const CurrentUser = ({ userName = 'TestUser' }: { userName?: string }) => ( ); const StyledListLink = styled('a')` - text-decoration: none; + ${({ theme }: { theme: typeof defaultTheme }) => css` +text-decoration: none; height: 40px; display: flex; align-items: center; - background: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.white)}; + background: (theme.colors.white)}; padding: 6px 12px; - color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.black)}; - border: 1px solid ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.grey_3)}; + color: ${theme.colors.black}; + border: 1px solid ${theme.colors.grey_3}; outline: none; font-size: 16px; cursor: pointer; width: 100%; &:hover { - background-color: ${({ theme }: { theme: typeof defaultTheme }) => css(theme.colors.grey_1)}; + background-color: ${theme.colors.grey_1}; } +`} `; const UserDropdown = () => { diff --git a/components/pages/repository/PageContent.tsx b/components/pages/repository/PageContent.tsx index f77fda92..021ce8ec 100644 --- a/components/pages/repository/PageContent.tsx +++ b/components/pages/repository/PageContent.tsx @@ -11,9 +11,11 @@ import defaultTheme from '../../theme'; import Footer from '../../Footer'; export const Collapsible = styled('div')` - border-top: 1px solid ${({ theme }: { theme: typeof defaultTheme }) => theme.colors.grey_2}; - height: 47px; - width: 100%; + ${({ theme }: { theme: typeof defaultTheme }) => css` + border-top: 1px solid ${theme.colors.grey_2}; + height: 47px; + width: 100%; + `} `; const PageContent = (props: PageContentProps) => { diff --git a/components/pages/user/index.tsx b/components/pages/user/index.tsx index b15c866d..4a90c75f 100644 --- a/components/pages/user/index.tsx +++ b/components/pages/user/index.tsx @@ -176,8 +176,7 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { }; const getDayValue = (exp: number) => { - // round or floor? - const days = Math.round(exp / 1000 / 60 / 60 / 24); + const days = Math.floor(exp / 1000 / 60 / 60 / 24); return `Expires in: ${days} days`; }; @@ -378,9 +377,13 @@ const ApiTokenInfo = ({ apiToken }: { apiToken: ApiToken | null }) => { ); }; +const StyledPageLayout = styled(PageLayout)` + background-color: ${theme.colors.white}; +`; + const User = () => { return ( - +
    css` @@ -446,7 +449,7 @@ const User = () => {
    - + ); };