Skip to content

Commit

Permalink
Restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
viktor-yakubiv committed May 20, 2020
1 parent 998401f commit 5a4e0ef
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 97 deletions.
117 changes: 24 additions & 93 deletions components/forms/request-premium/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import { classNames } from '@oacore/design/lib/utils'
import styles from './styles.module.css'

import { Button, TextField } from 'design'
import { requestPremium } from 'texts/letters'
import { withGlobalStore } from 'store'
import Markdown from 'components/markdown'

// TODO: Should be moved to @oacore/design
const Textarea = ({
Expand All @@ -23,12 +20,20 @@ const Textarea = ({
)

const Form = ({
userName,
userEmail,
organisationName,
organisationId,
className,
// data props
fromEmail,
fromName,
subject,
body,

// event handlers
onChange,
onSubmit,
onCancel,

// html props
children,
className,
...formProps
}) => {
// Showing name only when email is edited de
Expand All @@ -37,26 +42,20 @@ const Form = ({
// Showing message only when user decided to edit it
const [showMessage, toggleMessage] = useState(false)

const message = requestPremium.render({
userName,
organisationName,
organisationId,
})

const handleMessageToggle = useCallback(() => toggleMessage(true), [])

return (
<form
className={classNames
.use(styles.container, styles.backdrop)
.join(className)}
className={classNames.use(styles.container).join(className)}
onSubmit={onSubmit}
onChange={onChange}
{...formProps}
>
<div className={styles.personal}>
<TextField
type="email"
name="email"
defaultValue={userEmail}
defaultValue={fromEmail}
autoComplete="email"
label="Email"
helper="We will reach you back using this email address"
Expand All @@ -66,26 +65,22 @@ const Form = ({
{showName ? (
<TextField
name="name"
defaultValue={userName}
defaultValue={fromName}
autoComplete="name"
label="Name"
tag="p"
/>
) : (
<p>
<input type="hidden" name="name" value={userName} />
<input type="hidden" name="name" value={fromName} />
</p>
)}
</div>
{showMessage ? (
<Textarea
id="message"
name="message"
label="Message"
defaultValue={message}
/>
<Textarea id="body" name="body" label="Message" defaultValue={body} />
) : (
<p>
<input type="hidden" name="body" defaultValue={body} />
<Button
variant="outlined"
type="button"
Expand All @@ -96,6 +91,8 @@ const Form = ({
</p>
)}

{children}

<Button variant="contained">Send</Button>
{onCancel && (
<Button type="button" onClick={onCancel}>
Expand All @@ -106,70 +103,4 @@ const Form = ({
)
}

const FormController = withGlobalStore(({ store, ...formProps }) => {
// editing, sending, success, failure
const [status, changeStatus] = useState('editing')

const handleSubmit = useCallback(
(event) => {
event.preventDefault()
const formData = new FormData(event.target)
const data = Object.fromEntries(formData.entries())

changeStatus('sending')
store.sendContactRequest(data).then(
(success) => {
changeStatus(success ? 'success' : 'failure')
},
() => {
changeStatus('failure')
}
)
},
[store]
)

return (
<Form
action={new URL(store.contactUrl, process.env.API_URL)}
method="post"
actionStatus={status}
userName={store.user.name}
userEmail={store.user.email}
organisationName={store.organisation.name}
organisationId={store.organisation.id}
onSubmit={handleSubmit}
{...formProps}
/>
)
})

const RequestPremiumForm = ({ variant = 'button', template }) => {
const [formShown, toggleForm] = useState(variant === 'form')
const handleToggle = useCallback(() => toggleForm(!formShown), [formShown])

return (
<>
{template && (
<Markdown>
{template.render({
email: '[email protected]',
mailtoUrl: `mailto:[email protected]?subject=${encodeURIComponent(
'CORE Dashboard Premium for {{ organisation }}'
)}&body=${encodeURIComponent(
'Hello,\n\nI would like to request CORE Dashboard Premium for {{ organisationName }}\n\nSincerely,\nMatteo'
)}`,
})}
</Markdown>
)}
<p hidden={formShown} style={{ marginBottom: 0 }}>
<Button variant="contained" type="button" onClick={handleToggle}>
Request premium
</Button>
</p>
<FormController hidden={!formShown} onCancel={handleToggle} />
</>
)
}

export default RequestPremiumForm
export default Form
101 changes: 101 additions & 0 deletions modules/payments/billing-required-note/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { useEffect } from 'react'
import { autorun, observable } from 'mobx'
import { useObserver } from 'mobx-react-lite'

import { requestPremium as letter } from 'texts/letters'

const globalContext = observable({
user: {},
organisation: {},
})

const userInput = observable({
update(patch) {
Object.assign(this, patch)
},
})

const emailContext = observable({
toEmail: 'enterp\u0072is\u0065\u0040cor\u0065\u002e\u0061\u0063\u002euk',

// These 4 are mirrored from the global store
// with and React's effect and MobX's autorun in useSync()
organisationId: '',
organisationName: '',

get fromName() {
return userInput.name ?? globalContext.user?.name
},

get fromEmail() {
return userInput.email ?? globalContext.user?.email
},

get context() {
return {
userName: this.fromName,
userEmail: this.fromEmail,
organisationName: globalContext.organisation.name,
organisationId: globalContext.organisation.id,
}
},

get subject() {
return userInput.subject ?? letter.subject.render(this.context)
},

get body() {
return userInput.body ?? letter.body.render(this.context)
},

get mailtoUrl() {
const { email, subject, body } = this
const params = new URLSearchParams({
subject,
body,
})
return `m\u0061il\u0074\u006f\u003a${email}?${params}`
},

update(...args) {
userInput.update(...args)
},
})

const useSync = (store) =>
useEffect(
() =>
autorun(() => {
// Observing on root `user` and `organisation`` objects is intentional.
// It should prevent potential data overriding.
//
// However, it does not cover the case when the user changes the name
// and then makes the request.
globalContext.user = store.user
globalContext.organisation = store.organisation

emailContext.actionUrl = store.contactUrl
emailContext.send = store.sendContactRequest
}),
[store]
)

const useFormAction = () =>
useObserver(() => [emailContext.actionUrl, emailContext.send])

const useFormContext = () =>
useObserver(() => ({
subject: emailContext.subject,
body: emailContext.body,
fromName: emailContext.fromName,
fromEmail: emailContext.fromEmail,
}))

const useNoteContext = () =>
useObserver(() => ({
email: emailContext.email,
mailtoUrl: emailContext.mailtoUrl,
}))

export default emailContext
export { useFormAction, useFormContext, useNoteContext, useSync }
86 changes: 86 additions & 0 deletions modules/payments/billing-required-note/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React, { useState, useCallback } from 'react'

import data, {
useFormAction,
useFormContext,
useNoteContext,
useSync,
} from './data'

import { Button } from 'design'
import Markdown from 'components/markdown'
import Form from 'components/forms/request-premium'
import { withGlobalStore } from 'store'

const FormController = (formProps) => {
// editing, sending, success, failure
const [status, changeStatus] = useState('editing')

const context = useFormContext()
const [action, send] = useFormAction()

const handleSubmit = useCallback(
(event) => {
event.preventDefault()
const rawDataInput = new FormData(event.target)
const dataInput = Object.fromEntries(rawDataInput.entries())

changeStatus('sending')
send(dataInput).then(
(success) => {
changeStatus(success ? 'success' : 'failure')
},
() => {
changeStatus('failure')
}
)
},
[send]
)

// Populate changes on blur only to prevent live editing in two places
const handleChange = useCallback((event) => {
const { name, value } = event.target
data.update({ [name]: value })
})

return (
<Form
action={action}
method="post"
status={status}
{...context}
onSubmit={handleSubmit}
onBlur={handleChange}
{...formProps}
/>
)
}

const BillingRequiredNote = ({
store,
children,
template,
variant = 'button',
}) => {
const [formShown, toggleForm] = useState(variant === 'form')
const handleToggle = useCallback(() => toggleForm(!formShown), [formShown])
const context = useNoteContext()

useSync(store)

return (
<>
{template && <Markdown>{template.render(context)}</Markdown>}
{children}
<p hidden={formShown} style={{ marginBottom: 0 }}>
<Button variant="contained" type="button" onClick={handleToggle}>
Request premium
</Button>
</p>
<FormController hidden={!formShown} onCancel={handleToggle} />
</>
)
}

export default withGlobalStore(BillingRequiredNote)
Empty file added modules/payments/index.js
Empty file.
2 changes: 1 addition & 1 deletion templates/deposit-compliance/cards/table-card.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Table from 'components/table'
import ExportButton from 'components/export-button'
import * as texts from 'texts/depositing'
import DocumentLink from 'components/document-link'
import RequestPremium from 'components/forms/request-premium'
import RequestPremium from 'modules/payments/billing-required-note'

const SidebarContent = ({ context: { oai, originalId, authors, title } }) => {
const { Header, Body, Footer } = Table.Sidebar
Expand Down
13 changes: 10 additions & 3 deletions texts/letters/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import Template from '../template'
import requestPremiumSrc from './request-premium.txt'

const requestPremium = new Template(
const subjectSrc = requestPremiumSrc.attributes.subject
const bodySrc = requestPremiumSrc.body

const requestPremium = {
...requestPremiumSrc.attributes,

subject: new Template(subjectSrc),

// Removing '\n' from the end
requestPremiumSrc.slice(0, requestPremiumSrc.length - 1)
)
body: new Template(bodySrc.slice(0, bodySrc.length - 1)),
}

export default { requestPremium }
export { requestPremium }
Loading

0 comments on commit 5a4e0ef

Please sign in to comment.