Skip to content

Commit

Permalink
VPN support contact form UI from design
Browse files Browse the repository at this point in the history
  • Loading branch information
petemill authored and bsclifton committed Apr 9, 2022
1 parent c6a07fb commit 714edf9
Show file tree
Hide file tree
Showing 14 changed files with 443 additions and 91 deletions.
12 changes: 9 additions & 3 deletions components/brave_vpn/resources/panel/api/panel_browser_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import * as BraveVPN from 'gen/brave/components/brave_vpn/brave_vpn.mojom.m.js'
// Provide access to all the generated types
export * from 'gen/brave/components/brave_vpn/brave_vpn.mojom.m.js'

export type SupportData = BraveVPN.ServiceHandler_GetSupportData_ResponseParams

interface API {
pageCallbackRouter: BraveVPN.PageCallbackRouter
panelHandler: BraveVPN.PanelHandlerRemote
serviceHandler: BraveVPN.ServiceHandlerRemote
pageCallbackRouter: BraveVPN.PageInterface
panelHandler: BraveVPN.PanelHandlerInterface
serviceHandler: BraveVPN.ServiceHandlerInterface
}

let panelBrowserAPIInstance: API
Expand All @@ -35,3 +37,7 @@ export default function getPanelBrowserAPI () {
}
return panelBrowserAPIInstance
}

export function setPanelBrowserApiForTesting (api: API) {
panelBrowserAPIInstance = api
}
Original file line number Diff line number Diff line change
@@ -1,54 +1,115 @@
import * as React from 'react'
import { Button } from 'brave-ui'

import Button from '$web-components/button'
import Select from '$web-components/select'
import TextInput, { Textarea } from '$web-components/input'
import Toggle from '$web-components/toggle'
import { getLocale } from '../../../../../common/locale'
import * as S from './style'
import getPanelBrowserAPI from '../../api/panel_browser_api'
import getPanelBrowserAPI, * as BraveVPN from '../../api/panel_browser_api'
import { CaratStrongLeftIcon } from 'brave-ui/components/icons'

interface Props {
closeContactSupport: React.MouseEventHandler<HTMLButtonElement>
onCloseContactSupport: React.MouseEventHandler<HTMLButtonElement>
}

interface ContactSupportState {
interface ContactSupportInputFields {
contactEmail: string
problemSubject: string
problemBody: string
}

interface ContactSupportToggleFields {
shareHostname: boolean
shareAppVersion: boolean
shareOsVersion: boolean
}

type ContactSupportState = ContactSupportInputFields &
ContactSupportToggleFields

const defaultSupportState: ContactSupportState = {
contactEmail: '',
problemSubject: '',
problemBody: '',
shareHostname: true,
shareAppVersion: true,
shareOsVersion: true
}

type FormElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
type BaseType = string | number | React.FormEvent<FormElement>

function ContactSupport (props: Props) {
const [supportData, setSupportData] = React.useState('TODO: fill me in')
const [supportData, setSupportData] = React.useState<BraveVPN.SupportData>()
const [formData, setFormData] = React.useState<ContactSupportState>(defaultSupportState)
const [showErrors, setShowErrors] = React.useState(false)
// Undefined for never sent, true for is sending, false for has completed
const [isSubmitting, setIsSubmitting] = React.useState<boolean>()
const [, setRemoteSubmissionError] = React.useState<string>()

React.useEffect(async () => {
setSupportData(await getPanelBrowserAPI().getSupportData())
})
// Get possible values to submit
React.useEffect(() => {
getPanelBrowserAPI().serviceHandler.getSupportData()
.then(setSupportData)
}, [])

const [formData, setFormData] = React.useState<ContactSupportState>({
contactEmail: '',
problemSubject: '',
problemBody: '',
shareHostname: true,
shareAppVersion: true,
shareOsVersion: true
})
function getOnChangeField<T extends BaseType = BaseType> (key: keyof ContactSupportInputFields) {
return function (e: T) {
console.log(setFormData)
const value = (typeof e === 'string' || typeof e === 'number') ? e : e.currentTarget.value
if (formData[key] === value) {
return
}
setFormData(data => ({
...data,
[key]: value
}))
}
}

function getOnChangeToggle (key: keyof ContactSupportToggleFields) {
return function (isOn: boolean) {
if (formData[key] === isOn) {
return
}
setFormData(data => ({
...data,
[key]: isOn
}))
}
}

const isValid = React.useMemo(() => {
return !!formData?.problemBody
}, [formData])
return !supportData ||
!!formData.problemBody ||
!!formData.contactEmail ||
!!formData.problemSubject
}, [formData, supportData])

const handleSubmit = () => {
// Build submit data
}
const handleSubmit = async () => {
// Clear error about last submission
setRemoteSubmissionError(undefined)
// Handle submission when not valid, show user
// which fields are required
if (!isValid) {
setShowErrors(true)
return
}
// Handle is valid, submit data
setIsSubmitting(true)
const fullIssueBody = formData.problemBody + '\n' +
(formData.shareOsVersion ? `OS: ${supportData?.osVersion}\n` : '') +
(formData.shareAppVersion ? `App version: ${supportData?.appVersion}\n` : '') +
(formData.shareHostname ? `Hostname: ${supportData?.hostname}\n` : '')

await getPanelBrowserAPI().serviceHandler.createSupportTicket(
formData.contactEmail,
formData.problemSubject,
fullIssueBody
)

const onChangeBody = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setFormData({
...formData,
problemBody: event.currentTarget.value
})
console.log('changed')
// TODO: handle error case, if any?
setIsSubmitting(false)
}

return (
Expand All @@ -57,58 +118,99 @@ function ContactSupport (props: Props) {
<S.PanelHeader>
<S.BackButton
type='button'
onClick={props.closeContactSupport}
onClick={props.onCloseContactSupport}
aria-label='Close support form'
>
<i><CaratStrongLeftIcon /></i>
<span>{getLocale('braveVpnContactSupport')}</span>
</S.BackButton>
</S.PanelHeader>
<S.List>
<li>
Your email address
</li>
<li>

<S.Form onSubmit={e => { e.preventDefault() }}>
<TextInput
label={'Your email address'}
isRequired={true}
isErrorAlwaysShown={showErrors}
value={formData.contactEmail ?? ''}
onChange={getOnChangeField('contactEmail')}
/>
<label>
Subject
<select value={formData?.problemSubject || ''} name="issue" id="contact-support-issue">
<Select
ariaLabel={'Please choose a reason'}
value={formData.problemSubject ?? ''}
onChange={getOnChangeField('problemSubject')}
>
<option value="" disabled>Please choose a reason</option>
<option value="cant-connect">Cannot connect to the VPN (Other error)</option>
<option value="no-internet">No internet when connected</option>
<option value="slow">Slow connection</option>
<option value="website">Website doesn't work</option>
<option value="other">Other</option>
</select>
</li>
<li>
Describe your issue
<textarea
style={{ marginTop: '10px' }}
data-test-id={'contactSupportBody'}
cols={100}
rows={10}
value={formData?.problemBody || ''}
onChange={onChangeBody}
/>
</li>
<li>Please select the information you're comfortable sharing with us</li>
<li>VPN hostname: {supportData.os_version}</li>
<li>App version:</li>
<li>OS version:</li>
<li>
The more information you share with us the easier it will be for the support
staff to help you resolve your issue.

Support provided with the help of the Guardian team.
</li>
</S.List>
<Button
level='primary'
type='accent'
brand='rewards'
text='Submit'
disabled={!isValid}
onClick={handleSubmit}
/>
</Select>
</label>
<Textarea
value={formData.problemBody}
label={'Describe your issue'}
isRequired={true}
onChange={getOnChangeField('problemBody')}
/>
<S.OptionalValues>
<S.SectionDescription>
Please select the information you're comfortable sharing with us
</S.SectionDescription>
<S.OptionalValueLabel>
<div className={'optionalValueTitle'}>
<span className={'optionalValueTitleKey'}>VPN hostname:</span> {supportData?.hostname}
</div>
<Toggle
isOn={formData.shareHostname}
size={'sm'}
onChange={getOnChangeToggle('shareHostname')}
/>
</S.OptionalValueLabel>
<S.OptionalValueLabel>
<div className={'optionalValueTitle'}>
<span className={'optionalValueTitleKey'}>App version:</span> {supportData?.appVersion}
</div>
<Toggle
isOn={formData.shareAppVersion}
size={'sm'}
onChange={getOnChangeToggle('shareAppVersion')}
/>
</S.OptionalValueLabel>
<S.OptionalValueLabel>
<div className={'optionalValueTitle'}>
<span className={'optionalValueTitleKey'}>OS version:</span> {supportData?.osVersion}
</div>
<Toggle
isOn={formData.shareOsVersion}
size={'sm'}
onChange={getOnChangeToggle('shareOsVersion')}
/>
</S.OptionalValueLabel>
</S.OptionalValues>
<S.Notes>
<p>
The more information you share with us the easier it will be for the support staff to help you resolve your issue.
</p>
<p>
Support provided with the help of the Guardian team.
</p>
</S.Notes>
<Button
type={'submit'}
isPrimary
isCallToAction
isLoading={isSubmitting}
isDisabled={isSubmitting}
onClick={handleSubmit}
>
Submit
</Button>
</S.Form>

{/* TODO(petemill): show an error if remoteSubmissionError has value */}
</S.PanelContent>
</S.Box>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,16 @@ export const Box = styled.div`
height: 100%;
background: ${(p) => p.theme.color.panelBackground};
overflow: hidden;
font-family: ${p => p.theme.fontFamily.heading};
`

export const List = styled.ul`
list-style-type: none;
export const Form = styled.form`
padding: 0;
margin: 0;
text-align: center;
li {
margin-bottom: 28px;
&:last-child {
margin-bottom: 0;
}
}
margin: 20px 0 0 0;
display: flex;
flex-direction: column;
gap: 28px;
color: var(--text2);
a {
font-family: ${(p) => p.theme.fontFamily.heading};
Expand All @@ -30,14 +25,58 @@ export const List = styled.ul`
}
`

export const Card = styled.li`
--divider-color: ${(p) => p.theme.color.divider01};
border: 1px solid var(--divider-color);
border-radius: 8px;
export const SectionDescription = styled.p`
margin: 0;
color: var(--text2);
font-size: 14px;
line-height: 20px;
`

export const OptionalValues = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`

export const OptionalValueLabel = styled.label`
cursor: pointer;
display: flex;
flex-direction: row;
justify-content: stretch;
align-items: flex-start;
gap: 8px;
.optionalValueTitle {
flex: 1 1 0;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0;
font-size: 12px;
line-height: 18px;
color: var(--text1);
.optionalValueTitleKey {
color: var(--text3);
}
}
`

export const Notes = styled.div`
display: flex;
flex-direction: column;
gap: 18px;
p {
margin: 0;
font-size: 12px;
line-height: 18px;
color: var(--text3);
}
`

export const PanelContent = styled.section`
padding: 25px 24px 25px 24px;
padding: 25px 24px;
z-index: 2;
`

Expand Down
Loading

0 comments on commit 714edf9

Please sign in to comment.