Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded contribution form #325

Merged
merged 19 commits into from
Sep 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
3e692e4
style(Contribute): add form elements
newick Jun 7, 2019
ce8cb82
feat(contribution): setup sendinblue for contribution forward
JalilArfaoui Jun 20, 2019
2091ba0
feat(form): add redux-form
lutangar Jun 24, 2019
ee2a8ea
feat(form): add Fields adapter
lutangar Jun 24, 2019
e188cb5
feat(submit-notice): implement submit notice screen and success redir…
lutangar Jun 24, 2019
aecbf0a
fix(sendinblue): handle all 2xx codes
lutangar Jul 9, 2019
835646e
chore(forms): refactor with new conventions
lutangar Jul 10, 2019
c152a04
fix(stories): fix AddNoticeLink story
lutangar Jul 11, 2019
a7d2426
feat(contribution-form): show form errors when a field is touched and…
lutangar Aug 4, 2019
2501aa7
feat(contribution-form): find global error manually depending on each…
lutangar Aug 16, 2019
69de70e
chore(contribution-form): remove unused prop
lutangar Aug 16, 2019
9edb128
chore(contribution-form): move form related side effects to condition
lutangar Aug 16, 2019
6a93b21
chore(contribution-form): fix requiredPaths typing
lutangar Aug 16, 2019
b06a690
chore(form): rename `Errors` model to `ValidationErrors`
lutangar Aug 23, 2019
710e252
chore(contribution-form): add space between preview buttons
lutangar Aug 23, 2019
45352cb
fix(contribution): show contributor name on preview screen
lutangar Sep 2, 2019
b5e4d31
fix(contribution): fix global error not showing up anymore on the pre…
lutangar Sep 2, 2019
4a17a7e
chore(contribution): update lockfile after rebase
lutangar Sep 5, 2019
b95b018
style(contribution): fix typing issue
JalilArfaoui Sep 5, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SEND_CONTRIBUTION_TO=contact@lmem.net
SEND_CONTRIBUTION_FROM=contact@lmem.net
SEND_IN_BLUE_TOKEN=token
SENTRY_DSN=https://ID@sentry.io/PROJECT
2 changes: 2 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
// We don't actually always explicitly member accessility (componentDidMount ...)
"@typescript-eslint/explicit-member-accessibility": false,
"@typescript-eslint/no-explicit-any": ["error", { "ignoreRestArgs": true }],
// See SendInBlue types
"@typescript-eslint/no-empty-interface": false,
"@typescript-eslint/no-unused-vars": [
"error",
{ "ignoreRestSiblings": true }
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ platforms/
.ftppass
storybook-static/*
.sentryclirc
.env
6 changes: 4 additions & 2 deletions .storybook/config.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { configure, addDecorator } from '@storybook/react';
import React from 'react';
import { Provider } from 'react-redux';
import { createGlobalStyle, ThemeProvider } from 'styled-components';
import store from './store';
import theme from '../src/app/theme';
import 'typeface-lato';
import 'typeface-sedgwick-ave';
Expand All @@ -12,12 +14,12 @@ const Global = createGlobalStyle`
`;

addDecorator(getStory => (
<>
<Provider store={store}>
<Global />
<ThemeProvider theme={theme}>
<>{getStory()}</>
</ThemeProvider>
</>
</Provider>
));

// automatically import all files ending in *.stories.js
Expand Down
8 changes: 8 additions & 0 deletions .storybook/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { combineReducers, createStore } from 'redux';
import { reducer as form } from 'redux-form';

export default createStore(
combineReducers({
form
})
);
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"@types/react-redux": "^7.0.9",
"@types/react-router": "^4.4.4",
"@types/react-router-dom": "^4.3.1",
"@types/redux-form": "^8.1.5",
"@types/redux-immutable-state-invariant": "^2.1.0",
"@types/redux-logger": "^3.0.7",
"@types/remote-redux-devtools": "^0.5.3",
Expand Down Expand Up @@ -115,6 +116,7 @@
"core-js": "^3.2.1",
"css-loader": "^2.0.1",
"date-fns": "^1.30.1",
"dotenv": "^8.0.0",
"enzyme": "^3.7.0",
"es6-promise": "^3.2.1",
"eslint": "^5.16.0",
Expand Down Expand Up @@ -148,6 +150,7 @@
"pug": "^2.0.3",
"pug-loader": "^2.4.0",
"ramda": "^0.26.1",
"ramda-adjunct": "^2.19.0",
"react": "16.8.0",
"react-dev-utils": "^9.0.3",
"react-dom": "16.8.0",
Expand All @@ -157,6 +160,7 @@
"react-spring": "^8.0.20",
"redbox-react": "^1.6.0",
"redux": "^4.0.1",
"redux-form": "^8.2.6",
"redux-immutable-state-invariant": "^2.1.0",
"redux-logger": "^3.0.6",
"redux-persist": "^5.10.0",
Expand All @@ -180,7 +184,6 @@
"svg-url-loader": "^2.0.2",
"ts-node": "^8.3.0",
"tsconfig-paths": "^3.8.0",
"ramda-adjunct": "^2.19.0",
"typeface-lato": "^0.0.54",
"typeface-sedgwick-ave": "^0.0.54",
"typescript": "^3.5.3",
Expand Down
25 changes: 25 additions & 0 deletions src/api/sendInBlue/call.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fetch from 'isomorphic-fetch';

type Method = 'GET' | 'POST';

const call = (path: string, data?: {} | [], method: Method = 'GET') =>
fetch(`https://api.sendinblue.com/v3/${path}`, {
method,
body: JSON.stringify(data),
headers: {
'api-key': process.env.SEND_IN_BLUE_TOKEN as string,
'Content-Type': 'application/json'
}
}).then(response => {
if (response.status >= 400) {
throw new Error('Bad response from server');
}
if (response.status >= 200 && response.status < 300) {
return true;
}
return response.json();
});

export const get = (path: string) => call(path);

export const post = (path: string, data: {} | []) => call(path, data, 'POST');
6 changes: 6 additions & 0 deletions src/api/sendInBlue/sendEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { post } from './call';
import { TransactionEmail } from 'SendInBlue';

const sendEmail = (email: TransactionEmail) => post('smtp/email', email);

export default sendEmail;
34 changes: 34 additions & 0 deletions src/api/sendInBlue/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
declare module 'SendInBlue' {
export interface PersonEmail {
name?: string;
email: string;
}
export interface Sender extends PersonEmail {}
export interface Recipient extends PersonEmail {}

export interface UrlAttachment {
name?: string;
url: string;
}
export interface Base64Attachment {
name: string;
content: string;
}
export type Attachment = UrlAttachment | Base64Attachment;

export interface TransactionEmail {
sender?: Sender; // Mandatory if 'templateId' is not passed. Pass name (optional) and email of sender from which emails will be sent. For example, {'name':'Mary from MyShop', 'email':'no-reply@myshop.com'}
to: Recipient[]; // List of email addresses and names (optional) of the recipients. For example, [{'name':'Jimmy', 'email':'jimmy98@example.com'}, {'name':'Joe', 'email':'joe@example.com'}]
bcc?: Recipient[]; // List of email addresses and names (optional) of the recipients in bcc
cc?: Recipient[]; // List of email addresses and names (optional) of the recipients in cc
htmlContent?: string; // HTML body of the message ( Mandatory if 'templateId' is not passed, ignored if 'templateId' is passed )
textContent?: string; // Plain Text body of the message ( Ignored if 'templateId' is passed )
subject?: string; // Subject of the message. Mandatory if 'templateId' is not passed
replyTo?: Sender; // Email (required), along with name (optional), on which transactional mail recipients will be able to reply back. For example, {'email':'ann6533@example.com', 'name':'Ann'}.
attachment?: Attachment[]; // Pass the absolute URL (no local file) or the base64 content of the attachment along with the attachment name (Mandatory if attachment content is passed). For example, [{"url":"https://attachment.domain.com/myAttachmentFromUrl.jpg", "name":"My attachment 1"}, {"content":"base64 exmaple content", "name":"My attachment 2"}]. Allowed extensions for attachment file: xlsx, xls, ods, docx, docm, doc, csv, pdf, txt, gif, jpg, jpeg, png, tif, tiff, rtf, bmp, cgm, css, shtml, html, htm, zip, xml, ppt, pptx, tar, ez, ics, mobi, msg, pub, eps, odt, mp3, m4a, m4v, wma, ogg, flac, wav, aif, aifc, aiff, mp4, mov, avi, mkv, mpeg, mpg and wmv ( If 'templateId' is passed and is in New Template Language format then only attachment url is accepted. If template is in Old template Language format, then 'attachment' is ignored )
headers?: { [key: string]: string }; // Pass the set of headers that shall be sent along the mail headers in the original email. 'sender.ip' header can be set (only for dedicated ip users) to mention the IP to be used for sending transactional emails. For example, {'Content-Type':'text/html', 'charset':'iso-8859-1', 'sender.ip':'1.2.3.4'}
templateId?: number; // int64 Id of the template
params?: {}; // Pass the set of attributes to customize the template. For example, {'FNAME':'Joe', 'LNAME':'Doe'}. It's considered only if template is in New Template Language format.
tags?: string[]; // Tag your emails to find them more ea
}
}
44 changes: 44 additions & 0 deletions src/app/actions/contribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Contribution } from 'app/lmem/notice';
import { BaseAction, FormAction, FormMeta, ErrorAction } from '.';

export interface SubmitContributionAction extends FormAction {
type: 'CONTRIBUTION/SUBMIT';
payload: Contribution;
}
export const submitContribution = (
notice: Contribution,
meta: FormMeta
): SubmitContributionAction => ({
type: 'CONTRIBUTION/SUBMIT',
payload: notice,
meta
});

export interface ContributionSubmittedAction extends BaseAction {
type: 'CONTRIBUTION/SUBMITTED';
payload: Contribution;
}
export const contributionSubmitted = (
contribution: Contribution
): ContributionSubmittedAction => ({
type: 'CONTRIBUTION/SUBMITTED',
payload: contribution
});

export interface ContributionSubmissionFailed extends ErrorAction {
type: 'CONTRIBUTION/SUBMISSION_FAILED';
}
export const contributionSubmissionFailed = (
e: Error
): ContributionSubmissionFailed => ({
type: 'CONTRIBUTION/SUBMISSION_FAILED',
payload: e,
error: true
});

export interface ContributionSubmissionCanceled extends BaseAction {
type: 'CONTRIBUTION/SUBMISSION_CANCELED';
}
export const contributionSubmissionCanceled = (): ContributionSubmissionCanceled => ({
type: 'CONTRIBUTION/SUBMISSION_CANCELED'
});
28 changes: 28 additions & 0 deletions src/app/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ import {
NoticesFoundAction,
MarkNoticeReadAction
} from './notices';
import {
ContributionSubmissionFailed,
ContributionSubmittedAction,
SubmitContributionAction
} from './contribution';
import {
RemoveUITitleAction,
SetUITitleAction
Expand Down Expand Up @@ -87,6 +92,18 @@ export const createErrorAction = (type: unknown = 'ERROR') => (
meta
});

export interface FormAction extends BaseAction {
payload: {};
meta: ActionMeta & FormMeta;
}

export type TabFormMeta = TabMeta & FormMeta;

export interface TabFormAction extends TabAction {
payload: {};
meta: TabFormMeta;
}

export interface TabMeta extends ActionMeta {
tab: Tab;
}
Expand All @@ -97,6 +114,14 @@ export interface TabAction extends BaseAction {

export type TabErrorAction = TabAction & ErrorAction;

export type TabFormErrorAction = TabFormAction & ErrorAction;

export interface FormMeta {
form: string;
resolve: (...args: any[]) => void;
reject: (...args: any[]) => void;
}

export interface ActionMeta {
sendToBackground?: boolean;
sendToTab?: boolean;
Expand Down Expand Up @@ -136,6 +161,9 @@ export type AppAction =
| MarkNoticeReadAction
| SetUITitleAction
| RemoveUITitleAction
| SubmitContributionAction
| ContributionSubmittedAction
| ContributionSubmissionFailed
| OptionsRequestedAction
| OptionsTabOpened
| ListeningActionsReadyAction
Expand Down
2 changes: 1 addition & 1 deletion src/app/actions/notices.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StatefulNotice } from 'app/lmem/notice';
import { BaseAction, ErrorAction, TabAction, TabMeta } from '.';
import Tab from 'app/lmem/Tab';
import { BaseAction, ErrorAction, TabAction, TabMeta } from '.';

export interface NoticesFoundAction extends TabAction {
type: 'NOTICES_FOUND';
Expand Down
6 changes: 3 additions & 3 deletions src/app/background/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// Early imports with high priority stuff involved, such as event listeners creation
import loadHeap from '../../lib/heap';
import prepareDraftPreview from '../lmem/draft-preview/main';
import { LMEM_BACKEND_ORIGIN } from '../constants/origins';
import { LMEM_BACKEND_ORIGIN } from 'app/constants/origins';
import onInstalled from 'app/actions/install';
import updateDraftNotices from 'app/actions/updateDraftNotices';
import { configureSentryScope, initSentry } from '../utils/sentry';
import { getInstallationDetails } from './selectors/prefs';
import { configureSentryScope, initSentry } from 'app/utils/sentry';
import { store } from './store';
import { getInstallationDetails } from './selectors/prefs';
import fetchContentScript from './services/fetchContentScript';
import { BackgroundState } from './reducers';

Expand Down
8 changes: 4 additions & 4 deletions src/app/background/middlewares/sendFeedback.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AppAction } from '../../actions';
import { Dispatch } from 'redux';
import { FeedbackOnNoticeAction } from '../../actions/notices';
import postRating from '../../../api/postRating';
import { RatingType } from '../../lmem/rating';
import postRating from 'api/postRating';
import { AppAction } from 'app/actions';
import { FeedbackOnNoticeAction } from 'app/actions/notices';
import { RatingType } from 'app/lmem/rating';

const isRatingAction = (action: AppAction): action is FeedbackOnNoticeAction =>
action.type === 'FEEDBACK_ON_NOTICE' &&
Expand Down
12 changes: 6 additions & 6 deletions src/app/background/sagas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import error from '../../sagas/error';
import listenActionsFromMessages from '../../sagas/listenActionsFromMessages';
import refreshMatchingContexts from './refreshMatchingContexts';
import refreshContributors from './refreshContributors';
import watchBrowserActionSaga from './browserAction.saga';
import openOptionsWhenRequestedSaga from './openOptions.saga';
import sendContributorsToOptionsSaga from './sendContributorsToOptions.saga';
import browserAction from './browserAction.saga';
import openOptions from './openOptions.saga';
import sendContributorsToOptions from './sendContributorsToOptions.saga';

export default function* rootSaga() {
yield all([
Expand All @@ -19,9 +19,9 @@ export default function* rootSaga() {
fork(badge(theme.badge)),
fork(listenActionsFromMessages('background')),
fork(externalMessage),
fork(watchBrowserActionSaga),
fork(openOptionsWhenRequestedSaga),
fork(sendContributorsToOptionsSaga),
fork(browserAction),
fork(openOptions),
fork(sendContributorsToOptions),
fork(error)
]);
}
25 changes: 25 additions & 0 deletions src/app/background/services/createContributionEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Contribution } from 'app/lmem/notice';
import {
formatContributionHtmlEmail,
formatContributionTextEmail
} from 'app/lmem/format/contribution';
import { truncateWords } from '../../utils/truncate';
import { TransactionEmail } from 'SendInBlue';

const { SEND_CONTRIBUTION_FROM, SEND_CONTRIBUTION_TO } = process.env as AppEnv;

const createContributionEmail = (
contribution: Contribution
): TransactionEmail => ({
sender: { email: SEND_CONTRIBUTION_FROM },
to: [{ email: SEND_CONTRIBUTION_TO }],
replyTo: contribution.contributor,
subject: `${contribution.contributor.name}: ${truncateWords(
8,
contribution.message
)} !`,
htmlContent: formatContributionHtmlEmail(contribution),
textContent: formatContributionTextEmail(contribution)
});

export default createContributionEmail;
2 changes: 1 addition & 1 deletion src/app/background/store/migrations/4.2019-08-28.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PersistedState } from 'redux-persist/es/types';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WTF I just had this problem in another branch when attempting to push.... isn't that supposed to be in develop?

import { PersistedState } from 'redux-persist';
import { PersistedBackgroundState } from '../../reducers';
import * as RA from 'ramda-adjunct';
import { StateV3 } from './StateV3';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { MemoryRouter as Router } from 'react-router-dom';
import Notification from 'components/organisms/Notification';
import ContributeScreen from './ContributeScreen';

storiesOf('screens/Contribute/Submit', module)
.addDecorator(getStory => (
<Router>
<Notification title="créer une bulle ici">{getStory()}</Notification>
</Router>
))
.add('normal', () => <ContributeScreen />);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

import { ContentWrapper } from 'components/atoms';
import SubmitContributionForm from './SubmitContributionForm';

export const ContributeScreen = () => (
<ContentWrapper>
<SubmitContributionForm />
</ContentWrapper>
);

export default ContributeScreen;
Loading