Skip to content
Snippets Groups Projects
Commit 75cb59ce authored by Maarten de Waard's avatar Maarten de Waard :angel:
Browse files

Merge branch '19-make-contact-form-submission-possible' into 'main'

Resolve "Make contact form submission possible"

Closes #27 and #19

See merge request !14
parents 53743793 3831bba4
No related branches found
No related tags found
1 merge request!14Resolve "Make contact form submission possible"
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/style-copied-from-website.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Web site created using create-snowpack-app" /> <title>Helpful contact form | Development version</title>
<title>Snowpack App</title>
</head> </head>
<body> <body>
<div style="border: 2pt solid red; font-size: 200%; padding: 5pt;">Warning: this is a development version of <div style="border: 2pt solid red; font-size: 200%; padding: 5pt;">Warning: this is a development version of
...@@ -14,28 +14,6 @@ ...@@ -14,28 +14,6 @@
for actual support.</div> for actual support.</div>
<div lang="en" id="helpful-contact-form" style="width: 40rem; margin:10rem auto 0 auto"></div> <div lang="en" id="helpful-contact-form" style="width: 40rem; margin:10rem auto 0 auto"></div>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="/dist/index.js"></script> <script type="module" src="/bundle.js"></script>
<script>
if ('%MODE%' === 'development') {
// we wrap this in a function to prevent the variables from leaking out
(function () {
console.log('[dev] injecting greenhost stylesheet..')
var greenhostCss = document.createElement('link');
greenhostCss.setAttribute('rel', 'stylesheet');
greenhostCss.setAttribute('href', '/style-copied-from-website.css');
document.head.appendChild(greenhostCss);
}())
}
</script>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body> </body>
</html> </html>
...@@ -105,14 +105,7 @@ export const App: React.FC = () => { ...@@ -105,14 +105,7 @@ export const App: React.FC = () => {
<h1> <h1>
<Markdown>{t('contact.title')}</Markdown> <Markdown>{t('contact.title')}</Markdown>
</h1> </h1>
<ContactForm {...{ problem, problemGroup }} /> <ContactForm problem={problem} problemGroup={problemGroup} formAction={config.formAction} />
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
</Suspense> </Suspense>
); );
} }
......
...@@ -41,7 +41,7 @@ describe('<ContactForm problemGroup="%s"}>', () => { ...@@ -41,7 +41,7 @@ describe('<ContactForm problemGroup="%s"}>', () => {
'renders the contact form with problemGroup: %s', 'renders the contact form with problemGroup: %s',
(problemGroup) => { (problemGroup) => {
const { getByRole, queryByRole, getByText } = render( const { getByRole, queryByRole, getByText } = render(
<ContactForm {...{ problemGroup, problem }} />, <ContactForm formAction='' problem={problem} problemGroup={problemGroup} />,
); );
const contactDetailFieldset = getByRole('group', { const contactDetailFieldset = getByRole('group', {
name: /Contact details/i, name: /Contact details/i,
...@@ -73,7 +73,7 @@ describe('<ContactForm problemGroup="%s"}>', () => { ...@@ -73,7 +73,7 @@ describe('<ContactForm problemGroup="%s"}>', () => {
])('confirmation modal closes on %s %s', (action, value) => { ])('confirmation modal closes on %s %s', (action, value) => {
const problemGroup = 'technical'; const problemGroup = 'technical';
const { getByRole } = render( const { getByRole } = render(
<ContactForm {...{ problemGroup, problem }} />, <ContactForm formAction='' problem={problem} problemGroup={problemGroup} />,
); );
const regularSubmit = getByRole('button', { name: /submit request/i }); const regularSubmit = getByRole('button', { name: /submit request/i });
...@@ -94,21 +94,10 @@ describe('<ContactForm problemGroup="%s"}>', () => { ...@@ -94,21 +94,10 @@ describe('<ContactForm problemGroup="%s"}>', () => {
expect(modal).not.toBeInTheDocument(); expect(modal).not.toBeInTheDocument();
}); });
it('can be submitted by a form submit event', ()=>{
const problemGroup = 'technical';
const { getByRole } = render(
<ContactForm {...{ problemGroup, problem }} />,
);
const form = getByRole("form", {name:"Contact form"});
fireEvent.submit(form);
const modal = getByRole('dialog', { name: /thank you/i });
expect(modal).toBeInTheDocument();
});
it('has an option to make the request urgent', () => { it('has an option to make the request urgent', () => {
const problemGroup = 'technical'; const problemGroup = 'technical';
const { getByText, getByRole } = render( const { getByText, getByRole } = render(
<ContactForm {...{ problemGroup, problem }} />, <ContactForm formAction='' problem={problem} problemGroup={problemGroup} />,
); );
const urgencyCheckbox = getByRole('checkbox', { const urgencyCheckbox = getByRole('checkbox', {
name: /urgent request/i, name: /urgent request/i,
......
import React, { ChangeEvent, FormEvent, useState } from 'react'; import React, { ChangeEvent, MouseEventHandler, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import UrgentRequest from '../urgent-request/UrgentRequest'; import UrgentRequest from '../urgent-request/UrgentRequest';
import { CSSTransition, SwitchTransition } from 'react-transition-group';
import styles from './ContactForm.module.scss'; import styles from './ContactForm.module.scss';
import panelTransition from '../../styles/transitions/panel.module.scss'; import panelTransition from '../../styles/transitions/panel.module.scss';
import Fieldset from '../fieldset/Fieldset'; import Fieldset from '../fieldset/Fieldset';
...@@ -9,10 +8,12 @@ import Input from '../input/Input'; ...@@ -9,10 +8,12 @@ import Input from '../input/Input';
import Modal from '../modal/Modal'; import Modal from '../modal/Modal';
export interface ContactFormProps { export interface ContactFormProps {
formAction: string;
problemGroup: string; problemGroup: string;
problem: string; problem: string;
} }
export const ContactForm: React.FC<ContactFormProps> = ({ export const ContactForm: React.FC<ContactFormProps> = ({
formAction,
problemGroup, problemGroup,
problem, problem,
}: ContactFormProps) => { }: ContactFormProps) => {
...@@ -31,54 +32,47 @@ export const ContactForm: React.FC<ContactFormProps> = ({ ...@@ -31,54 +32,47 @@ export const ContactForm: React.FC<ContactFormProps> = ({
setUrgentIntent(event.target.checked); setUrgentIntent(event.target.checked);
}; };
const submit = (event: FormEvent<HTMLFormElement>) => { const submit: MouseEventHandler<HTMLButtonElement> = (event) => {
event.preventDefault(); if (process.env.NODE_ENV !== 'production') {
sendRequest(); event.preventDefault();
console.log('Not submitting if not in production mode.');
const formData = { ...state, ...{ urgent: urgentIntent ? 'yes' : 'no' } };
setModalContent('form contents: ' + JSON.stringify(formData));
}
}; };
const sendRequest = (urgent = false) => {
const formData = { ...state, ...{ urgent: urgent ? 'yes' : 'no' } };
Object.fromEntries(
Object.entries(formData).map(([name, value]) => [
`SupportForm[${name}]`,
value,
]),
);
setModalContent(JSON.stringify(formData));
};
// For use in the CSSTransition, see
// https://github.com/reactjs/react-transition-group/issues/668#issuecomment-695162879
const submitRef = React.useRef<any>(null);
return ( return (
<form onSubmit={submit} name="HelpfulContactForm" className={`${styles.ContactForm} cosmosForm`} aria-label={t("Contact form")}> <form
action={formAction}
name="HelpfulContactForm"
className={`${styles.ContactForm} cosmosForm`}
aria-label={t("Contact form")}
>
<Fieldset legend={t('contact.details')}> <Fieldset legend={t('contact.details')}>
<Input <Input
label={t('contact.name')} label={t('contact.name')}
id="name" id="name"
name="name" name="HelpfulContactForm[name]"
required required
onChange={changeHandler} onChange={changeHandler}
/> />
<Input <Input
label={t('contact.phone')} label={t('contact.phone')}
id="phone" id="phone"
name="phone" name="HelpfulContactForm[phone]"
onChange={changeHandler} onChange={changeHandler}
/> />
<Input <Input
label={t('contact.identification')} label={t('contact.identification')}
id="identification" id="identification"
name="identification" name="HelpfulContactForm[identification]"
onChange={changeHandler} onChange={changeHandler}
required required
/> />
<Input <Input
label={t('contact.email')} label={t('contact.email')}
id="email" id="email"
name="email" name="HelpfulContactForm[email]"
onChange={changeHandler} onChange={changeHandler}
required required
/> />
...@@ -87,7 +81,7 @@ export const ContactForm: React.FC<ContactFormProps> = ({ ...@@ -87,7 +81,7 @@ export const ContactForm: React.FC<ContactFormProps> = ({
<Input <Input
label={t('Message')} label={t('Message')}
id="message" id="message"
name="message" name="HelpfulContactForm[message]"
onChange={changeHandler} onChange={changeHandler}
type="textarea" type="textarea"
required required
...@@ -105,36 +99,25 @@ export const ContactForm: React.FC<ContactFormProps> = ({ ...@@ -105,36 +99,25 @@ export const ContactForm: React.FC<ContactFormProps> = ({
</div> </div>
)} )}
<div className="field"> <div className="field">
<SwitchTransition> <div>
<CSSTransition <input
key={urgentIntent ? 'urgent' : 'normal'} name="HelpfulContactForm[urgency]"
in={urgentIntent} value={urgentIntent ? 'urgent' : 'normal'}
classNames={{ ...panelTransition }} type="hidden"
nodeRef={submitRef} />
unmountOnExit={true} {urgentIntent && (
mountOnEnter={true} <UrgentRequest submit={submit}/>
addEndListener={(done: () => void) => { )}
// use the css transitionend event to mark the finish of a transition {!urgentIntent && (
submitRef.current?.addEventListener('transitionend', done, false); <button
}} className="good"
> type="submit"
<div ref={submitRef}> onClick={submit}
{urgentIntent ? ( >
<UrgentRequest sendUrgent={() => sendRequest(true)} /> {t('submit')}
) : ( </button>
<button )}
className="good" </div>
onClick={(event) => {
event.preventDefault();
sendRequest();
}}
>
{t('submit')}
</button>
)}
</div>
</CSSTransition>
</SwitchTransition>
</div> </div>
{modalContent && ( {modalContent && (
<Modal <Modal
......
...@@ -3,13 +3,13 @@ import { render } from '@testing-library/react'; ...@@ -3,13 +3,13 @@ import { render } from '@testing-library/react';
import UrgentRequest from './UrgentRequest'; import UrgentRequest from './UrgentRequest';
import userEvent from '@testing-library/user-event'; import userEvent from '@testing-library/user-event';
const mockSendUrgent = jest.fn(); const mockSubmit: React.MouseEventHandler<HTMLButtonElement> = jest.fn().mockImplementation((e) => e.preventDefault());
describe('<UrgentRequest>', () => { describe('<UrgentRequest>', () => {
it('renders the option to make a contact request urgent', () => { it('renders the option to make a contact request urgent', () => {
const { getByRole } = render( const { getByRole } = render(
<UrgentRequest <UrgentRequest
sendUrgent={mockSendUrgent} submit={mockSubmit}
/>, />,
); );
const urgentFieldset = getByRole('group'); const urgentFieldset = getByRole('group');
...@@ -38,6 +38,6 @@ describe('<UrgentRequest>', () => { ...@@ -38,6 +38,6 @@ describe('<UrgentRequest>', () => {
// Can submit now.. // Can submit now..
userEvent.click(urgentSubmit); userEvent.click(urgentSubmit);
// Triggers a message sent up the hierarchy.. // Triggers a message sent up the hierarchy..
expect(mockSendUrgent).toBeCalled(); expect(mockSubmit).toBeCalled();
}); });
}); });
import Markdown from 'markdown-to-jsx'; import Markdown from 'markdown-to-jsx';
import React, { ChangeEvent, useEffect, useState } from 'react'; import React, { ChangeEvent, MouseEventHandler, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import styles from './UrgentRequest.module.scss'; import styles from './UrgentRequest.module.scss';
export interface UrgentRequestProps { export interface UrgentRequestProps {
sendUrgent: CallableFunction; submit: MouseEventHandler<HTMLButtonElement>;
} }
export const UrgentRequest: React.FC<UrgentRequestProps> = ({ export const UrgentRequest: React.FC<UrgentRequestProps> = ({
sendUrgent, submit,
}: UrgentRequestProps) => { }: UrgentRequestProps) => {
const [urgent, setUrgent] = useState<boolean>(false); const [urgent, setUrgent] = useState<boolean>(false);
const [urgentText, setUrgentText] = useState<string>(''); const [urgentText, setUrgentText] = useState<string>('');
...@@ -36,10 +36,8 @@ export const UrgentRequest: React.FC<UrgentRequestProps> = ({ ...@@ -36,10 +36,8 @@ export const UrgentRequest: React.FC<UrgentRequestProps> = ({
<button <button
disabled={!urgent} disabled={!urgent}
className="bad" className="bad"
onClick={(event) => { type="submit"
event?.preventDefault(); onClick={submit}
sendUrgent();
}}
> >
{t('urgency.submit')} {t('urgency.submit')}
</button> </button>
......
# This determines where to mount the "app" in the DOM. # This determines where to mount the "app" in the DOM.
mountingElementId: 'helpful-contact-form' mountingElementId: 'helpful-contact-form'
formAction: 'https://service.greenhost.net/site/helpfulContact'
#formAction: 'http://cosmos2.local/site/helpfulContact'
urgentRequestFee: 420 urgentRequestFee: 420
localisations: localisations:
en: en:
......
...@@ -2,6 +2,15 @@ const CopyPlugin = require("copy-webpack-plugin"); ...@@ -2,6 +2,15 @@ const CopyPlugin = require("copy-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path'); const path = require('path');
staticFiles = [
{ from: "public/style-copied-from-website.css" },
];
if (process.env.NODE_ENV === 'development') {
staticFiles.append(
{ from: "public/index.html" }
);
}
module.exports = { module.exports = {
entry: './src/index.tsx', entry: './src/index.tsx',
plugins: [ plugins: [
...@@ -11,14 +20,15 @@ module.exports = { ...@@ -11,14 +20,15 @@ module.exports = {
ignoreOrder: false, // Enable to remove warnings about conflicting order ignoreOrder: false, // Enable to remove warnings about conflicting order
}), }),
new CopyPlugin({ new CopyPlugin({
patterns: [ patterns: staticFiles,
{ from: "public/style-copied-from-website.css" },
],
options: { options: {
concurrency: 100, concurrency: 100,
}, },
}), }),
], ],
devServer: {
contentBase: path.join(__dirname, 'public'),
},
module: { module: {
rules: [ rules: [
{ {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment