UserDropdown.jsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. // @flow
  15. import React from 'react'
  16. import { Link } from 'react-router-dom'
  17. import { observer } from 'mobx-react'
  18. import styled, { css } from 'styled-components'
  19. import autobind from 'autobind-decorator'
  20. import Palette from '../../styleUtils/Palette'
  21. import StyleProps from '../../styleUtils/StyleProps'
  22. import { navigationMenu } from '../../../constants'
  23. import type { User } from '../../../types/User'
  24. import userImage from './images/user.svg'
  25. import userWhiteImage from './images/user-white.svg'
  26. import configLoader from '../../../utils/Config'
  27. const Wrapper = styled.div`
  28. position: relative;
  29. `
  30. const Icon = styled.div`
  31. position: relative;
  32. cursor: pointer;
  33. width: 32px;
  34. height: 32px;
  35. transition: all ${StyleProps.animations.swift};
  36. background: url('${props => props.white ? userWhiteImage : userImage}') no-repeat center;
  37. &:hover {
  38. opacity: 0.8;
  39. }
  40. `
  41. const List = styled.div`
  42. background: ${Palette.grayscale[1]};
  43. border-radius: ${StyleProps.borderRadius};
  44. position: absolute;
  45. right: 0;
  46. top: 45px;
  47. padding: 16px;
  48. display: flex;
  49. flex-direction: column;
  50. z-index: 10;
  51. `
  52. const ListItem = styled.div`
  53. padding-top: 8px;
  54. `
  55. const Label = styled.div`
  56. display: inline-block;
  57. white-space: nowrap;
  58. ${props => props.selectable ? css`
  59. cursor: pointer;
  60. &:hover {
  61. color: ${Palette.primary};
  62. }
  63. ` : ''}
  64. `
  65. const ListHeader = styled.div`
  66. position: relative;
  67. &:after {
  68. content: ' ';
  69. position: absolute;
  70. width: 10px;
  71. height: 10px;
  72. background: ${Palette.grayscale[1]};
  73. border: 1px solid ${Palette.grayscale[1]};
  74. border-color: transparent transparent ${Palette.grayscale[1]} ${Palette.grayscale[1]};
  75. transform: rotate(135deg);
  76. right: -6px;
  77. top: -22px;
  78. transition: all ${StyleProps.animations.swift};
  79. }
  80. `
  81. const Username = styled(Link)`
  82. font-size: 16px;
  83. color: ${Palette.black};
  84. text-decoration: none;
  85. ${props => props.to === '' ? 'pointer-events: none;' : ''}
  86. &:hover {color: ${Palette.primary};}
  87. `
  88. const Email = styled.div`
  89. font-size: 10px;
  90. color: ${Palette.grayscale[4]};
  91. margin-top: 8px;
  92. padding-bottom: 8px;
  93. border-bottom: 1px solid ${Palette.grayscale[3]};
  94. `
  95. type DictItem = { label: string, value: string }
  96. type Props = {
  97. onItemClick: (item: DictItem) => void,
  98. user: ?User,
  99. white?: boolean,
  100. }
  101. type State = {
  102. showDropdownList: boolean,
  103. }
  104. @observer
  105. class UserDropdown extends React.Component<Props, State> {
  106. state = {
  107. showDropdownList: false,
  108. }
  109. itemMouseDown: boolean
  110. componentDidMount() {
  111. window.addEventListener('mousedown', this.handlePageClick, false)
  112. }
  113. componentWillUnmount() {
  114. window.removeEventListener('mousedown', this.handlePageClick, false)
  115. }
  116. handleItemClick(item: DictItem) {
  117. if (this.props.onItemClick) {
  118. this.props.onItemClick(item)
  119. }
  120. this.setState({ showDropdownList: false })
  121. }
  122. @autobind
  123. handlePageClick() {
  124. if (!this.itemMouseDown) {
  125. this.setState({ showDropdownList: false })
  126. }
  127. }
  128. handleButtonClick() {
  129. this.setState({ showDropdownList: !this.state.showDropdownList })
  130. }
  131. renderNoUser() {
  132. if (this.props.user) {
  133. return null
  134. }
  135. return <Label>No signed in user</Label>
  136. }
  137. renderListHeader() {
  138. if (!this.props.user) {
  139. return null
  140. }
  141. let href: ?string
  142. let isAdmin = this.props.user.isAdmin
  143. if (isAdmin && navigationMenu.find(m => m.value === 'users'
  144. && !configLoader.config.disabledPages.find(p => p === 'users') && (!m.requiresAdmin || isAdmin))) {
  145. href = `/user/${this.props.user.id}`
  146. }
  147. return (
  148. <ListHeader
  149. onMouseDown={() => { this.itemMouseDown = true }}
  150. onMouseUp={() => { this.itemMouseDown = false }}
  151. >
  152. <Username
  153. data-test-id="userDropdown-username"
  154. to={href || ''}
  155. >{this.props.user.name}</Username>
  156. <Email>{this.props.user.email}</Email>
  157. </ListHeader>
  158. )
  159. }
  160. renderList() {
  161. if (!this.state.showDropdownList) {
  162. return null
  163. }
  164. let items = [{
  165. label: 'About Coriolis',
  166. value: 'about',
  167. }, {
  168. label: 'Sign Out',
  169. value: 'signout',
  170. }]
  171. let list = (
  172. <List>
  173. <ListHeader>
  174. {this.renderListHeader()}
  175. {this.renderNoUser()}
  176. </ListHeader>
  177. {this.props.user ? items.map(item => {
  178. return (
  179. <ListItem
  180. key={item.value}
  181. onMouseDown={() => { this.itemMouseDown = true }}
  182. onMouseUp={() => { this.itemMouseDown = false }}
  183. >
  184. <Label selectable onClick={() => { this.handleItemClick(item) }} data-test-id={`userDropdown-label-${item.value}`}>{item.label}</Label>
  185. </ListItem>
  186. )
  187. }) : null}
  188. </List>
  189. )
  190. return list
  191. }
  192. render() {
  193. return (
  194. <Wrapper {...this.props}>
  195. <Icon
  196. onMouseDown={() => { this.itemMouseDown = true }}
  197. onMouseUp={() => { this.itemMouseDown = false }}
  198. onClick={() => this.handleButtonClick()}
  199. white={this.props.white}
  200. data-test-id="userDropdown-button"
  201. />
  202. {this.renderList()}
  203. </Wrapper>
  204. )
  205. }
  206. }
  207. export default UserDropdown