diff options
author | Grégoire Aubert <gregoire.aubert@sonarsource.com> | 2018-06-15 18:14:31 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-06-21 20:21:29 +0200 |
commit | 043271f2a737a9400676a321b07d57d90038b597 (patch) | |
tree | badc2e592097b5e993157a65c0087e8f7610b420 /server/sonar-web/src/main | |
parent | 4e91bd432aeef4a2e7ff680910907e62007ac801 (diff) | |
download | sonarqube-043271f2a737a9400676a321b07d57d90038b597.tar.gz sonarqube-043271f2a737a9400676a321b07d57d90038b597.zip |
SONARCLOUD-66 Move and improve contact form
Diffstat (limited to 'server/sonar-web/src/main')
8 files changed, 332 insertions, 10 deletions
diff --git a/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx b/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx index d1ffc010768..05568542aeb 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx @@ -52,7 +52,7 @@ export default function GlobalFooterSonarCloud() { <a href="https://community.sonarsource.com/c/help/sc">{translate('footer.help')}</a> </li> <li className="page-footer-menu-item"> - <Link to="/contact">{translate('footer.contact_us')}</Link> + <Link to="/about/contact">{translate('footer.contact_us')}</Link> </li> <li className="page-footer-menu-item"> <a href="https://sonarcloud.statuspage.io/">{translate('footer.status')}</a> diff --git a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap index 89bb14e6625..42385bd4293 100644 --- a/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap +++ b/server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap @@ -75,7 +75,7 @@ exports[`should render correctly 1`] = ` <Link onlyActiveOnIndex={false} style={Object {}} - to="/contact" + to="/about/contact" > footer.contact_us </Link> diff --git a/server/sonar-web/src/main/js/app/styles/components/alerts.css b/server/sonar-web/src/main/js/app/styles/components/alerts.css index 6cbd7fb0896..0695abad28f 100644 --- a/server/sonar-web/src/main/js/app/styles/components/alerts.css +++ b/server/sonar-web/src/main/js/app/styles/components/alerts.css @@ -60,6 +60,11 @@ color: #3c763d; } +.alert-big { + font-size: var(--mediumFontSize); + padding: 10px 16px; +} + .page-notifs .alert { padding: 8px 10px; } diff --git a/server/sonar-web/src/main/js/app/theme.js b/server/sonar-web/src/main/js/app/theme.js index 75f6dcdc4d1..b9e4c9670cc 100644 --- a/server/sonar-web/src/main/js/app/theme.js +++ b/server/sonar-web/src/main/js/app/theme.js @@ -111,11 +111,12 @@ module.exports = { // sonarcloud sonarcloudOrange: '#f60', sonarcloudOrangeDark: '#e65c00', - sonarcloudFontFamily: - "Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif", sonarcloudBlack200: '#f9f9fb', sonarcloudBlack300: '#cfd3d7', sonarcloudBlack700: '#434447', sonarcloudBlack800: '#2d3032', - sonarcloudBlack900: '#070706' + sonarcloudBlack900: '#070706', + sonarcloudBorderGray: 'rgba(207, 211, 215, 0.5)', + sonarcloudFontFamily: + "Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif" }; diff --git a/server/sonar-web/src/main/js/apps/about/routes.ts b/server/sonar-web/src/main/js/apps/about/routes.ts index a042681c66c..315b604da61 100644 --- a/server/sonar-web/src/main/js/apps/about/routes.ts +++ b/server/sonar-web/src/main/js/apps/about/routes.ts @@ -30,6 +30,10 @@ const routes = [ childRoutes: isSonarCloud ? [ { + path: 'contact', + component: lazyLoad(() => import('./sonarcloud/Contact')) + }, + { path: 'sq', childRoutes: [ { indexRoute: { component: lazyLoad(() => import('./sonarcloud/SQHome')) } }, diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/Contact.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/Contact.tsx new file mode 100644 index 00000000000..dd0e78da829 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/Contact.tsx @@ -0,0 +1,189 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { Link } from 'react-router'; +import SonarCloudPage from './SonarCloudPage'; +import Select from '../../../components/controls/Select'; +import { isLoggedIn, Organization } from '../../../app/types'; +import './style.css'; + +const CATEGORIES = [ + { label: 'Commercial', value: 'commercial' }, + { label: 'Product', value: 'product' }, + { label: 'Operations / Service / Infrastructure', value: 'operations' } +]; + +interface State { + category: string; + organization: string; + subject: string; +} + +export default class Contact extends React.PureComponent<{}, State> { + state: State = { category: '', organization: '', subject: '' }; + + getOrganizations = (organizations?: Organization[]) => { + return (organizations || []).map(org => ({ + label: org.name, + value: org.key + })); + }; + + handleCategoryChange = ({ value }: { value: string }) => { + this.setState({ category: value }); + }; + + handleOrganizationChange = ({ value }: { value: string }) => { + this.setState({ organization: value }); + }; + + handleSubjectChange = (event: React.ChangeEvent<HTMLInputElement>) => { + this.setState({ subject: event.currentTarget.value }); + }; + + render() { + return ( + <SonarCloudPage> + {({ currentUser, userOrganizations }) => ( + <div className="page page-limited sc-page sc-contact-page"> + <h1 className="sc-page-title">Contact us</h1> + <p className="alert alert-warning alert-big display-inline-block"> + If you are looking for help with SonarCloud, our{' '} + <a + href="https://community.sonarsource.com/c/help/sc" + rel="noopener noreferrer" + target="_blank"> + <strong>Support forum</strong> + </a>{' '} + is the best place to get help. + </p> + <br /> + <p className="alert alert-warning alert-big display-inline-block"> + Please contact us only if you couldn't solve your problem with the forum help. + </p> + {!isLoggedIn(currentUser) && ( + <p> + You can{' '} + <Link to={{ pathname: '/sessions/new', query: { return_to: '/about/contact' } }}> + log in to SonarCloud + </Link>{' '} + to automatically fill this form information and get better support. + </p> + )} + <form action="https://formspree.io/contact@sonarcloud.io" method="POST"> + <div className="form-group"> + <label htmlFor="contact-name">Name</label> + <input + autoFocus={true} + defaultValue={isLoggedIn(currentUser) ? currentUser.name : ''} + id="contact-name" + name="name" + required={true} + type="text" + /> + </div> + <div className="form-group"> + <label htmlFor="contact-email">Email</label> + <input + defaultValue={isLoggedIn(currentUser) ? currentUser.email : ''} + id="contact-email" + name="_replyto" + required={true} + type="email" + /> + </div> + <div className="form-group category-select"> + <label htmlFor="contact-category">Category</label> + <Select + id="contact-category" + name="category" + onChange={this.handleCategoryChange} + options={CATEGORIES} + placeholder="Choose a category" + required={true} + searchable={false} + value={this.state.category} + /> + <input + className="category-select-helper" + required={true} + tabIndex={-1} + value={this.state.category} + /> + </div> + {isLoggedIn(currentUser) && ( + <div className="form-group category-select"> + <label htmlFor="contact-organization">Organization concerned by the issue</label> + <Select + id="contact-organization" + name="organization" + onChange={this.handleOrganizationChange} + options={this.getOrganizations(userOrganizations)} + placeholder="Choose an organization" + searchable={false} + value={this.state.organization} + /> + </div> + )} + <div className="form-group"> + <label htmlFor="contact-subject">Subject</label> + <input + id="contact-subject" + maxLength={70} + onChange={this.handleSubjectChange} + required={true} + type="text" + value={this.state.subject} + /> + <input + name="_subject" + type="hidden" + value={`[${this.state.category}] ${this.state.subject}`} + /> + </div> + <div className="form-group"> + <label htmlFor="contact-question">How can we help?</label> + <textarea + className="form-control" + id="contact-question" + name="question" + placeholder="Please describe precisely what is your issue..." + required={true} + rows={8} + /> + </div> + <div className="form-group"> + { + // The following hidden input field must absolutely be kept + // This is a "honeypot" field to avoid spam by fooling scrapers + } + <input name="_gotcha" type="text" /> + <button type="submit">Send Request</button> + </div> + {isLoggedIn(currentUser) && ( + <input name="login" type="hidden" value={currentUser.login} /> + )} + </form> + </div> + )} + </SonarCloudPage> + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/Footer.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/Footer.tsx index f7634e2479f..4ee9a593fe8 100644 --- a/server/sonar-web/src/main/js/apps/about/sonarcloud/Footer.tsx +++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/Footer.tsx @@ -43,7 +43,7 @@ export default function Footer() { className="sc-footer-link" rel="noopener noreferrer" target="_blank" - to="/contact"> + to="/about/contact"> Contact Us </Link> </li> diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/style.css b/server/sonar-web/src/main/js/apps/about/sonarcloud/style.css index f05b2e23fcb..40319514e6e 100644 --- a/server/sonar-web/src/main/js/apps/about/sonarcloud/style.css +++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/style.css @@ -517,7 +517,7 @@ a.sc-top-nav-link:focus { width: 600px; margin: 20px auto; padding: 30px; - border: 1px solid rgba(207, 211, 215, 0.5); + border: 1px solid var(--sonarcloudBorderGray); border-radius: 5px; box-shadow: 0 3px 3px 0 rgba(7, 7, 6, 0.05); } @@ -559,7 +559,7 @@ a.sc-top-nav-link:focus { } .sc-branch-feature + .sc-branch-feature { - border-top: 1px solid rgba(207, 211, 215, 0.5); + border-top: 1px solid var(--sonarcloudBorderGray); } .sc-branch-feature-left { @@ -585,7 +585,7 @@ a.sc-top-nav-link:focus { .sc-vsts-start { display: inline-flex; - border: 1px solid rgba(207, 211, 215, 0.5); + border: 1px solid var(--sonarcloudBorderGray); border-radius: 5px; box-shadow: 0 3px 3px 0 rgba(7, 7, 6, 0.05); } @@ -599,7 +599,7 @@ a.sc-top-nav-link:focus { } .sc-vsts-start-box + .sc-vsts-start-box { - border-left: 1px solid rgba(207, 211, 215, 0.5); + border-left: 1px solid var(--sonarcloudBorderGray); } .sc-vsts-start-title { @@ -607,3 +607,126 @@ a.sc-top-nav-link:focus { font-size: 21px; font-weight: 300; } + +.sc-contact-page label { + display: block; + max-width: 100%; + margin-bottom: 5px; + font-size: 14px; + font-weight: 700; +} + +.sc-contact-page input, +.sc-contact-page textarea { + display: block; + width: 100%; + max-width: 100%; + height: 30px; + padding: 6px 12px; + font-size: 14px; + font-family: inherit; + line-height: 1.42857143; + color: #555; + background-color: white; + border: 1px solid var(--gray80); + border-radius: 2px; + transition: border-color ease 0.3s; +} + +.sc-contact-page input:focus, +.sc-contact-page textarea:focus { + border-color: #4b9fd5; + outline: none !important; + box-shadow: none; +} + +.sc-contact-page input, +.sc-contact-page .Select { + width: 300px; +} + +.sc-contact-page .Select-control { + height: 30px; +} + +.sc-contact-page .Select-value, +.sc-contact-page .Select-placeholder { + margin-top: 4px; +} + +.sc-contact-page .Select-placeholder { + font-style: italic; + color: var(--disableGrayText); +} + +.sc-contact-page .Select-input { + box-shadow: none; +} + +.sc-contact-page .category-select { + position: relative; +} + +.sc-contact-page .category-select-helper { + opacity: 0; + z-index: -1; + position: absolute; + bottom: 0px; +} + +.sc-contact-page input[name='_gotcha'] { + display: none !important; +} + +.sc-contact-page textarea { + height: auto; +} + +.sc-contact-page button { + padding: 6px 12px; + background: none; + color: var(--sonarcloudOrange); + border: 1px solid var(--sonarcloudOrange); + border-radius: 5px; + font-size: 12px; + font-weight: 400; + cursor: pointer; +} + +.sc-contact-page button:hover { + color: white; + background-color: var(--sonarcloudOrange); +} + +.sc-contact-page button:focus { + color: white; + background-color: var(--sonarcloudOrange); + outline: none; +} + +.sc-contact-page button:active { + color: white; + background-color: var(--sonarcloudOrange); +} + +.sc-contact-page .form-group { + margin-bottom: 15px; +} + +.sc-contact-page .sc-page-title { + text-align: left; + margin-top: 0; +} + +.sc-contact-page p { + margin-bottom: 20px; +} + +.sc-contact-page a { + color: var(--sonarcloudOrange) !important; + border: none; +} + +.sc-contact-page a:hover { + color: #cc5200 !important; +} |