Skip to content

Commit

Permalink
Merge pull request #1 from markmur/v1
Browse files Browse the repository at this point in the history
V1
  • Loading branch information
markmur authored Aug 19, 2016
2 parents b30abfd + 5020938 commit 92f543f
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 64 deletions.
21 changes: 17 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ function sendToSlack(payload) {
// methods are available from this function. You should call the `sent`
// method if the request was successfully sent to Slack.
this.sent();
}, this.error);
}, error => {
this.error(error.statusText);
});
}

/**
Expand All @@ -73,7 +75,7 @@ function uploadImage(image) {
// If you've called the `uploadImage` function with `image => uploadImage(image)`,
// you'll have to use a ref on the SlackFeedback component to access the
// 'imageUploaded' and 'error' methods
res => this.imageUploaded(),
url => this.imageUploaded(url),
err => this.error(err)
);
}
Expand All @@ -82,13 +84,24 @@ function uploadImage(image) {

### Props
| Prop | Type | Default | Required | Description |
| ------------- | ------ |-------------|:-------------:|-------------|
| channel | string | | required | The Slack channel to send messages. Note: All slack channels are lowercase. The string should be identical to the channel name e.g '#feedback' |
|----------|--------|--------------|:-------------:|-------------|
| channel | string | | required | The Slack channel to send messages. Note: All slack channels are lowercase. The string should be identical to the channel name e.g '#feedback' |
| onSubmit | function | | required | A JSON payload object will be returned when the user submits the form. |
| onImageUpload | function | | | Method that will be called with a file argument |
| user | string | "Unknown User" | | The logged in user's name (if applicable) |
| emoji | string | 🗣 | | The emoji that will show in place of the users avatar on Slack |
| buttonText | string | "Slack Feedback" | | The text for the trigger button |
| disabled | boolean | false | | Disable the component entirely. Returns null. Can be used to disable the component on specific pages |

### Callback Functions
| Function | Arguments | Description |
|-----------|-----------|-------------|
| sent() | | Should be called when the payload has been successfully sent to your sever. The submit button will display a `Sent!` message and reset the loading state. |
| error() | error (string) | Should be called if there's an error sending the slack payload to your server. Pass the `statusText` of the response to update the submit button. |
| imageUploaded() | url (string) | Should be called if an image is successfully uploaded to your server. This adds the image url to the payload JSON and resets the loading state of the component. |
| uploadError() | error (string) | Should be called if there's an error uploading an image. |

___

### What does it look like?
![image](http://res.cloudinary.com/di0xuztdq/image/upload/v1471245001/uehkqqfarpue7auonqol.gif)
Expand Down
5 changes: 2 additions & 3 deletions lib/SlackFeedback.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion lib/SlackFeedback.js.map

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"express": "^4.14.0",
"file-loader": "^0.9.0",
"isomorphic-fetch": "^2.2.1",
"multer": "^1.2.0",
"node-sass": "^3.8.0",
"nodemon": "^1.10.0",
"react": "^15.3.0",
Expand All @@ -54,11 +55,7 @@
},
"dependencies": {
"classnames": "^2.2.5",
"es6-promise": "^3.2.1",
"multer": "^1.2.0",
"react-addons-update": "^15.3.0",
"react-dropzone": "^3.5.3",
"whatwg-fetch": "^1.0.0"
"es6-promise": "^3.2.1"
},
"peerDependencies": {
"react": "^0.14 || ^15.0.0-rc || ^15.0",
Expand Down
4 changes: 1 addition & 3 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,4 @@ app.post('/upload', upload.single('image'), (req, res) => {
});

// Listen
app.listen(3001, function () {
console.log('EXPRESS LISTENING ON PORT 3001');
});
app.listen(3001);
106 changes: 64 additions & 42 deletions src/SlackFeedback.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
import update from 'react-addons-update';
import Dropzone from 'react-dropzone';

// classnames
import classNames from 'classnames';
Expand All @@ -16,16 +13,16 @@ const propTypes = {
onImageUpload: PropTypes.func,
sending: PropTypes.bool,
user: PropTypes.string,
avatar: PropTypes.string,
disabled: PropTypes.bool,
emoji: PropTypes.string,
buttonText: PropTypes.string,
disableImageUpload: PropTypes.bool,
imageUploadText: PropTypes.string,
};

const defaultProps = {
sending: false,
user: 'Unknown User',
disabled: false,
emoji: ':speaking_head_in_silhouette:',
buttonText: 'Slack Feedback',
disableImageUpload: false,
Expand All @@ -52,8 +49,14 @@ class SlackFeedback extends Component {
image: {}
};

// Bind event handlers once to avoid performance issues with re-binding
// on every render
this.removeImage = this.removeImage.bind(this);
this.toggle = this.toggle.bind(this);
this.send = this.send.bind(this);
this.toggleSendURL = this.toggleSendURL.bind(this);
this.selectType = this.selectType.bind(this);
this.close = this.close.bind(this);
}

toggle() {
Expand Down Expand Up @@ -101,44 +104,43 @@ class SlackFeedback extends Component {
});
}

determineErrorType(err) {
if (!err) return 'ERROR!';

switch(err.status) {
case 400: return 'Bad Request!';
case 403: return 'Forbidden!';
case 404: return 'Channel Not Found!';
case 410: return 'Channel is Archived!';
case 500: return 'Server Error!';
default: return 'ERROR!';
}
sent() {
this.setState({
sending: false,
sent: true,
image: {},
error: false,
}, () => {
this.refs.message.value = '';
setTimeout(() => {
this.setState({ sent: false });
}, 5 * 1000);
});
}

error(err) {
console.warn(err);

this.setState({
sending: false,
error: this.determineErrorType(err)
}, () => {

setTimeout(() => {
this.setState({ error: null })
}, 8 * 1000);
});
}

sent() {
this.setState({
sending: false,
sent: true,
image: {},
error: false,
}, () => {
this.refs.message.value = '';
setTimeout(() => {
this.setState({ sent: false });
}, 5 * 1000);
});
determineErrorType(err) {
if (!err || typeof err !== 'string') return 'Unexpected Error!';

switch(err.status) {
case 400: return 'Bad Request!';
case 403: return 'Forbidden!';
case 404: return 'Channel Not Found!';
case 410: return 'Channel is Archived!';
case 500: return 'Server Error!';
default: return 'Unexpected Error!';
}
}

send() {
Expand Down Expand Up @@ -205,6 +207,21 @@ class SlackFeedback extends Component {
});
}

uploadError(err) {

this.setState({
uploading: false,
error: 'Error Uploading Image!'
}, () => {

this.removeImage();

setTimeout(() => {
this.setState({ error: null });
}, 6 * 1000);
});
}

imageUploaded(url) {
if (typeof url !== 'string') {
console.error('[SlackFeedback] `url` argument in `imageUploaded` method must be a string');
Expand All @@ -223,9 +240,10 @@ class SlackFeedback extends Component {
});
}

renderDropzone() {
if (this.props.disableImageUpload) return null;
if (this.state.image.preview) return null;
renderImageUpload() {
if (this.state.image.preview) {
return this.renderImagePreview();
}

return (
<div class="SlackFeedback-image-upload">
Expand Down Expand Up @@ -255,11 +273,12 @@ class SlackFeedback extends Component {

renderImagePreview() {
var { image, uploadingImage } = this.state;

if (!image.preview) return null;

return (
<div class="SlackFeedback--image-preview" style={{
backgroundImage: `url(${this.state.image.preview})`
backgroundImage: `url(${image.preview})`
}}>
{uploadingImage ?
<div class="SlackFeedback--loader"></div> :
Expand Down Expand Up @@ -289,12 +308,15 @@ class SlackFeedback extends Component {
if (sending && !sent) submitText = 'Sending Feedback...';
if (error) submitText = error;

// Return nothing if the component has been disabled
if (this.props.disabled) return null;

return (
<div ref="SlackFeedback" id="SlackFeedback" class={classNames('SlackFeedback', { active })}>
<div ref="container" class="SlackFeedback--container fadeInUp">
<div class="SlackFeedback--header">
<SlackIcon /> Send Feedback to Slack
<div class="close" onClick={::this.close}>close</div>
<div class="close" onClick={this.close}>close</div>
</div>

<div class="SlackFeedback--content">
Expand All @@ -304,31 +326,31 @@ class SlackFeedback extends Component {

<label class="SlackFeedback--label">Feedback Type</label>
<ul class="SlackFeedback--tabs">
<li onClick={::this.selectType} class={classNames({
<li onClick={this.selectType} class={classNames({
selected: selectedType === 'Bug'
})}>Bug</li>
<li onClick={::this.selectType} class={classNames({
<li onClick={this.selectType} class={classNames({
selected: selectedType === 'Feature'
})}>Feature</li>
<li onClick={::this.selectType} class={classNames({
<li onClick={this.selectType} class={classNames({
selected: selectedType === 'Improvement'
})}>Improvement</li>
</ul>

<label class="SlackFeedback--label">Your Message</label>
<textarea ref="message" class="SlackFeedback--textarea" placeholder="Message..." />

{this.renderDropzone()}
{this.renderImagePreview()}
{/* Only render the image upload if there's callback available */}
{this.props.onImageUpload ? this.renderImageUpload() : null}

<div style={{ padding: '0.5em 0 1em' }}>
<input id="sendURL" class="SlackFeedback--checkbox" type="checkbox" checked={sendURL} onChange={::this.toggleSendURL} />
<input id="sendURL" class="SlackFeedback--checkbox" type="checkbox" checked={sendURL} onChange={this.toggleSendURL} />
<label for="sendURL" class="SlackFeedback--checkbox-label">Send URL with Feedback</label>
</div>

<button
class={classNames('submit', { sent, error, disabled: sending || uploadingImage })}
onClick={::this.send}>
onClick={this.send}>
{submitText}
</button>
</div>
Expand Down
19 changes: 13 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ ReactDOM.render(
user="Mark Murray"
emoji=":bug:"
channel="#yab-feedback"
/>, document.getElementById('root'));
/>,
document.getElementById('root')
);

/**
* Send payload to server
Expand Down Expand Up @@ -48,9 +50,14 @@ function uploadImage(file) {
method: 'POST',
body: form
})
.then(res => res.json())
.then(url => {
console.debug(url);
this.imageUploaded(url);
});
.then(res => {
console.log(res.status, res.statusText);
if (res.status < 200 || res.status >= 300) {
this.uploadError(res.statusText);
}

return res.json();
})
.then(url => this.imageUploaded(url));

}

0 comments on commit 92f543f

Please sign in to comment.