aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main
diff options
context:
space:
mode:
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>2018-06-15 18:14:31 +0200
committerSonarTech <sonartech@sonarsource.com>2018-06-21 20:21:29 +0200
commit043271f2a737a9400676a321b07d57d90038b597 (patch)
treebadc2e592097b5e993157a65c0087e8f7610b420 /server/sonar-web/src/main
parent4e91bd432aeef4a2e7ff680910907e62007ac801 (diff)
downloadsonarqube-043271f2a737a9400676a321b07d57d90038b597.tar.gz
sonarqube-043271f2a737a9400676a321b07d57d90038b597.zip
SONARCLOUD-66 Move and improve contact form
Diffstat (limited to 'server/sonar-web/src/main')
-rw-r--r--server/sonar-web/src/main/js/app/components/GlobalFooterSonarCloud.tsx2
-rw-r--r--server/sonar-web/src/main/js/app/components/__tests__/__snapshots__/GlobalFooterSonarCloud-test.tsx.snap2
-rw-r--r--server/sonar-web/src/main/js/app/styles/components/alerts.css5
-rw-r--r--server/sonar-web/src/main/js/app/theme.js7
-rw-r--r--server/sonar-web/src/main/js/apps/about/routes.ts4
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/Contact.tsx189
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/Footer.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/about/sonarcloud/style.css131
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&apos;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;
+}