Skip to content
This repository has been archived by the owner on Mar 27, 2023. It is now read-only.

Commit

Permalink
[wip] broken specs on email sender
Browse files Browse the repository at this point in the history
  • Loading branch information
vincemtnz committed Apr 26, 2019
1 parent c36051d commit 8caf23e
Show file tree
Hide file tree
Showing 30 changed files with 585 additions and 70 deletions.
10 changes: 10 additions & 0 deletions app/assets/stylesheets/member-facing/global.scss
@@ -0,0 +1,10 @@
.container {
section {
padding: 2em 4%;
clear: both;
}

.alternate-bg {
background-color: #ececec;
}
}
16 changes: 16 additions & 0 deletions app/javascript/async/email-ukparliament.js
@@ -0,0 +1,16 @@
// @flow
import React from 'react';
import { render } from 'react-dom';
import { EmailPluginConfig } from './interfaces';
import EmailParliament from '../modules/EmailParliament';

type Options = {
el: HTMLElement,
config: EmailPluginConfig,
};
export const init = (options: Options) => {
if (!options.config.active) return;
if (options.el) {
render(<EmailParliament config={options.config} />, options.el);
}
};
6 changes: 0 additions & 6 deletions app/javascript/async/email-your-mp.js

This file was deleted.

48 changes: 42 additions & 6 deletions app/javascript/async/index.js
@@ -1,17 +1,53 @@
// @flow
import PluginError from '../util/plugin-error';

const MODULES = {
'email-your-mp': () => import('./email-your-mp'),
type PluginErrorOptions = {
name?: string,
message: string,
};
export default async (name: string, options: any) => {

export class PluginError {
name: string;
message: string;
constructor(options: PluginErrorOptions) {
this.name = options.name || 'PluginError';
this.message = options.message;
}

toString() {
return `${this.name}: ${this.message}`;
}
}

export const MODULES = {
'email-ukparliament': () => import('./email-ukparliament'),
};

// Lists supported async modules
export const list = (): string[] => Object.keys(MODULES);

// Loads an async module.
export const load = async (name: string, options: any) => {
const loader = MODULES[name];
if (!loader) {
throw new PluginError({
message: `Plugin "${name}" is not available or does not support dynamic loading.`,
});
}
const plugin = await loader();
plugin.init(options);
};

const plugin = await loader(options);
plugin.load(options);
export const modules = {
// Attaches the async feature to the champaign global
// object.
setup(champaign: any) {
Object.assign(champaign, {
modules: {
load,
list,
},
});
},
};

export default modules;
Expand Up @@ -8,3 +8,8 @@ export interface PluginConfig {
pluginId: number;
title: string;
}

export interface EmailPluginConfig extends PluginConfig {
subject: string;
template: string;
}
6 changes: 6 additions & 0 deletions app/javascript/components/ProfilePicture/ProfilePicture.css
@@ -0,0 +1,6 @@
.ProfilePicture {
border-radius: 50%;
width: 70px;
height: 70px;
border: 2px solid papayawhip;
}
17 changes: 17 additions & 0 deletions app/javascript/components/ProfilePicture/index.js
@@ -0,0 +1,17 @@
// @flow
import React from 'react';
import classnames from 'classnames';
import './ProfilePicture.css';

type Props = {
src: string,
alt: string,
className?: string,
};

export default (props: Props) => {
if (!props.src) return null;
const classNames = classnames('ProfilePicture', props.className);

return <img className={classNames} src={props.src} alt={props.alt} />;
};
28 changes: 28 additions & 0 deletions app/javascript/member-facing/index.js
@@ -0,0 +1,28 @@
// @flow
import Petition from './backbone/petition';
import PetitionAndScrollToConsent from './backbone/petition_and_scroll_to_consent';
import DoubleOptIn from './double_opt_in';
import Survey from './backbone/survey';
import ActionForm from './backbone/action_form';
import OverlayToggle from './backbone/overlay_toggle';
import Thermometer from './backbone/thermometer';
import Sidebar from './backbone/sidebar';
import Notification from './backbone/notification';
import SweetPlaceholder from './backbone/sweet_placeholder';
import CampaignerOverlay from './backbone/campaigner_overlay';

export const setup = (champaign: any) => {
Object.assign(champaign, {
ActionForm,
CampaignerOverlay,
DoubleOptIn,
Notification,
OverlayToggle,
Petition,
PetitionAndScrollToConsent,
Sidebar,
Survey,
SweetPlaceholder,
Thermometer,
});
};
5 changes: 5 additions & 0 deletions app/javascript/modules/EmailParliament/EmailComposer.css
@@ -0,0 +1,5 @@
.EmailComposer {
max-width: 800px;
margin-left: auto;
margin-right: auto;
}
50 changes: 50 additions & 0 deletions app/javascript/modules/EmailParliament/EmailComposer.js
@@ -0,0 +1,50 @@
// @flow
// $FlowIgnore
import React, { useState } from 'react';
import SweetInput from '../../components/SweetInput/SweetInput';
import FormGroup from '../../components/Form/FormGroup';
import Editor from '../../components/EmailEditor/EmailEditor';
import type { Target } from './index';
import './EmailComposer.css';

type Props = {
postcode: string,
title: string,
subject: string,
template: string,
target: Target,
body?: string,
};

export default (props: Props) => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');

return (
<section className="EmailComposer form--big">
<h3 className="EmailComposer-title">{props.title}</h3>
<FormGroup>
<SweetInput
label="Full name"
name="fullName"
type="email"
value={name}
onChange={setName}
/>
</FormGroup>
<FormGroup>
<SweetInput
label="email"
name="email"
type="email"
value={email}
onChange={setEmail}
/>
</FormGroup>
<FormGroup>
<Editor subject={props.subject} body={props.template} errors={{}} />
</FormGroup>
<br style={{ clear: 'both' }} />
</section>
);
};
26 changes: 26 additions & 0 deletions app/javascript/modules/EmailParliament/RepresentativeCard.css
@@ -0,0 +1,26 @@
.RepresentativeCard {
display: flex;
margin-left: -4%;
margin-right: -4%;
padding: 8%;
background: white;
margin-top: 2em;
margin-bottom: 2em;
}

.RepresentativeCard-copy {
margin: auto 1em;
font-size: 1.3em;
}
.RepresentativeCard-name {
font-weight: bold;
}
.RepresentativeCard-party {
color: darkgrey;
}
.RepresentativeCard-email {
font-size: 0.8em;
color: grey;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;

}
42 changes: 42 additions & 0 deletions app/javascript/modules/EmailParliament/RepresentativeCard.js
@@ -0,0 +1,42 @@
// @flow
import React from 'react';
import ProfilePicture from '../../components/ProfilePicture';
import type { Target } from './index';
import './RepresentativeCard.css';

type Props = {
target?: Target,
};

export default (props: Props) => {
let target = props.target;
if (!target) target = test;
// const { target } = props;

return (
<div className="RepresentativeCard">
<ProfilePicture
src={target.picture}
alt={`Profile picture of ${target.displayAs}`}
/>
<div className="RepresentativeCard-copy">
<span className="RepresentativeCard-name">{target.displayAs} </span>
<span className="RepresentativeCard-party">{target.party}</span>
<br />
<span className="RepresentativeCard-email">email: {target.email} </span>
</div>
</div>
);
};

const test = {
email: 'stella.creasy.mp@parliament.uk',
name: 'Stella Creasy',
displayAs: 'Stella Creasy',
listAs: 'Creasy, Stella',
party: 'Labour (Co-op)',
picture:
'https://data.parliament.uk/membersdataplatform/services//images/MemberPhoto/4088/',
gender: 'F',
id: '4088',
};
8 changes: 8 additions & 0 deletions app/javascript/modules/EmailParliament/SearchByPostcode.css
@@ -0,0 +1,8 @@
.SearchByPostcode {
max-width: 450px;
margin: auto;
}
.SearchByPostcode .title {
margin: 1em 0;
}

74 changes: 74 additions & 0 deletions app/javascript/modules/EmailParliament/SearchByPostcode.js
@@ -0,0 +1,74 @@
// @flow
// $FlowIgnore
import React, { useState, useEffect } from 'react';
import Button from '../../components/Button/Button';
import SweetInput from '../../components/SweetInput/SweetInput';
import FormGroup from '../../components/Form/FormGroup';
import SearchError from './SearchError';
import { search } from './api';
import type { Target } from './index';

import './SearchByPostcode.css';

type Props = {
onChange: (target: Target) => void,
};

const SearchByPostcode = (props: Props) => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [postcode, setPostcode] = useState(null);
const [input, setInput] = useState('');

const reset = () => {
setLoading();
};
useEffect(
() => {
(async () => {
if (!postcode) return;

setLoading(true);
setError(false);

try {
const target = await search(postcode);
props.onChange(target);
} catch (e) {
setError(true);
} finally {
setLoading(false);
}
})();
},
[postcode]
);

const submit = (e: SyntheticEvent<HTMLFormElement>) => {
e.preventDefault();
setPostcode(input);
};

return (
<div className="SearchByPostcode">
<form className="action-form form--big" onSubmit={submit}>
<SearchError error={error} />
<FormGroup>
<h3 className="title">Enter your UK postcode</h3>
<SweetInput
type="text"
label="Postcode"
value={input}
onChange={setInput}
hasError={!!error}
/>
</FormGroup>
<Button disabled={loading} onClick={() => setPostcode(input)}>
Find your MP
</Button>
</form>
</div>
);
};

export default SearchByPostcode;
7 changes: 7 additions & 0 deletions app/javascript/modules/EmailParliament/SearchError.css
@@ -0,0 +1,7 @@
.SearchError {
background: #fff;
color: orangered;
border: 2px solid crimson;
border-radius: 2px;
padding: 0.5em 1em;
}
19 changes: 19 additions & 0 deletions app/javascript/modules/EmailParliament/SearchError.js
@@ -0,0 +1,19 @@
// @flow
import React from 'react';
import './SearchError.css';

type Props = {
error: boolean,
};

export default ({ error }: Props) => {
if (!error) return null;
return (
<div className="SearchError has-error">
<p>
We couldn't find your MP. Check that your postcode is correct, or get in
touch with us if you think there might be an issue.
</p>
</div>
);
};

0 comments on commit 8caf23e

Please sign in to comment.