You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Contact.tsx 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import * as React from 'react';
  21. import { Helmet } from 'react-helmet';
  22. import { Link } from 'react-router';
  23. import { Location } from 'history';
  24. import SQPageContainer from './components/SQPageContainer';
  25. import Select from '../../../components/controls/Select';
  26. import { Alert } from '../../../components/ui/Alert';
  27. import { isLoggedIn } from '../../../helpers/users';
  28. import './style.css';
  29. const CATEGORIES = [
  30. { label: 'Commercial', value: 'commercial' },
  31. { label: 'Confidential Request', value: 'confidential_request' }
  32. ];
  33. interface Props {
  34. location: Location;
  35. }
  36. interface State {
  37. category: string;
  38. organization: string;
  39. question: string;
  40. subject: string;
  41. }
  42. export default class Contact extends React.PureComponent<Props, State> {
  43. constructor(props: Props) {
  44. super(props);
  45. const { query } = props.location;
  46. this.state = {
  47. category: query.category || '',
  48. organization: query.organization || '',
  49. question: query.question || '',
  50. subject: query.subject || ''
  51. };
  52. }
  53. getOrganizations = (organizations?: T.Organization[]) => {
  54. return (organizations || []).map(org => ({
  55. label: org.name,
  56. value: org.key
  57. }));
  58. };
  59. handleCategoryChange = ({ value }: { value: string }) => {
  60. this.setState({ category: value });
  61. };
  62. handleOrganizationChange = ({ value }: { value: string }) => {
  63. this.setState({ organization: value });
  64. };
  65. handleSubjectChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  66. this.setState({ subject: event.currentTarget.value });
  67. };
  68. handleQuestionChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
  69. this.setState({ question: event.currentTarget.value });
  70. };
  71. render() {
  72. return (
  73. <SQPageContainer>
  74. {({ currentUser, userOrganizations }) => (
  75. <div className="page page-limited sc-page sc-contact-page">
  76. <Helmet title="Contact Us | SonarCloud">
  77. <meta
  78. content="If you are looking for help with SonarCloud, our Support forum is the best place to get help."
  79. name="description"
  80. />
  81. </Helmet>
  82. <h1 className="sc-page-title">Contact us</h1>
  83. <Alert display="inline" variant="warning">
  84. If you are looking for help with SonarCloud, our{' '}
  85. <a
  86. href="https://community.sonarsource.com/c/help/sc"
  87. rel="noopener noreferrer"
  88. target="_blank">
  89. <strong>Support forum</strong>
  90. </a>{' '}
  91. is the best place to get help.
  92. </Alert>
  93. <br />
  94. <Alert display="inline" variant="warning">
  95. Use this contact form for commercial or confidential requests only.
  96. </Alert>
  97. {!isLoggedIn(currentUser) && (
  98. <p>
  99. You can{' '}
  100. <Link to={{ pathname: '/sessions/new', query: { return_to: '/about/contact' } }}>
  101. log in to SonarCloud
  102. </Link>{' '}
  103. to automatically fill this form information and get better support.
  104. </p>
  105. )}
  106. <form action="https://formspree.io/contact@sonarcloud.io" method="POST">
  107. <div className="form-group">
  108. <label htmlFor="contact-name">Name</label>
  109. <input
  110. autoFocus={true}
  111. defaultValue={isLoggedIn(currentUser) ? currentUser.name : ''}
  112. id="contact-name"
  113. name="name"
  114. required={true}
  115. type="text"
  116. />
  117. </div>
  118. <div className="form-group">
  119. <label htmlFor="contact-email">Email</label>
  120. <input
  121. defaultValue={isLoggedIn(currentUser) ? currentUser.email : ''}
  122. id="contact-email"
  123. name="_replyto"
  124. required={true}
  125. type="email"
  126. />
  127. </div>
  128. <div className="form-group category-select">
  129. <label htmlFor="contact-category">Category</label>
  130. <Select
  131. id="contact-category"
  132. name="category"
  133. onChange={this.handleCategoryChange}
  134. options={CATEGORIES}
  135. placeholder="Choose a category"
  136. required={true}
  137. searchable={false}
  138. value={this.state.category}
  139. />
  140. <input
  141. className="category-select-helper"
  142. required={true}
  143. tabIndex={-1}
  144. value={this.state.category}
  145. />
  146. </div>
  147. {isLoggedIn(currentUser) && (
  148. <div className="form-group category-select">
  149. <label htmlFor="contact-organization">Organization concerned by the issue</label>
  150. <Select
  151. id="contact-organization"
  152. name="organization"
  153. onChange={this.handleOrganizationChange}
  154. options={this.getOrganizations(userOrganizations)}
  155. placeholder="Choose an organization"
  156. searchable={false}
  157. value={this.state.organization}
  158. />
  159. </div>
  160. )}
  161. <div className="form-group">
  162. <label htmlFor="contact-subject">Subject</label>
  163. <input
  164. id="contact-subject"
  165. maxLength={70}
  166. onChange={this.handleSubjectChange}
  167. required={true}
  168. type="text"
  169. value={this.state.subject}
  170. />
  171. <input
  172. name="_subject"
  173. type="hidden"
  174. value={`[${this.state.category}] ${this.state.subject}`}
  175. />
  176. </div>
  177. <div className="form-group">
  178. <label htmlFor="contact-question">How can we help?</label>
  179. <textarea
  180. className="form-control"
  181. id="contact-question"
  182. name="question"
  183. onChange={this.handleQuestionChange}
  184. placeholder="Please describe precisely what is your issue..."
  185. required={true}
  186. rows={8}
  187. value={this.state.question}
  188. />
  189. </div>
  190. <div className="form-group">
  191. {
  192. // The following hidden input field must absolutely be kept
  193. // This is a "honeypot" field to avoid spam by fooling scrapers
  194. }
  195. <input name="_gotcha" type="text" />
  196. <button type="submit">Send Request</button>
  197. </div>
  198. {isLoggedIn(currentUser) && (
  199. <input name="login" type="hidden" value={currentUser.login} />
  200. )}
  201. </form>
  202. </div>
  203. )}
  204. </SQPageContainer>
  205. );
  206. }
  207. }