added amount stored to support message

This commit is contained in:
Martina 2020-09-16 16:26:15 -07:00
parent 183affecad
commit d489c95c0b
13 changed files with 337 additions and 29 deletions

View File

@ -219,3 +219,10 @@ export const getSerializedProfile = async (data) => {
body: JSON.stringify({ data }),
});
};
export const createSupportMessage = async (data) => {
return await returnJSON(`/api/support-message`, {
...DEFAULT_OPTIONS,
body: JSON.stringify({ data }),
});
};

View File

@ -53,6 +53,7 @@ const constructSlatesTreeForNavigation = (slates) => {
name: s.data.name || s.slatename,
pageTitle: `Viewing ${s.slatename}`,
decorator: "SLATE",
ignore: true,
};
});
};
@ -77,7 +78,7 @@ export const generate = ({ library = [], slates = [] }) => [
decorator: "SLATES",
name: "Slates",
pageTitle: "Slates",
children: constructSlatesTreeForNavigation(slates),
children: null,
},
{
id: "V1_NAVIGATION_SLATE",
@ -87,6 +88,7 @@ export const generate = ({ library = [], slates = [] }) => [
children: null,
ignore: true,
},
...constructSlatesTreeForNavigation(slates),
constructFilesTreeForNavigation(library),
/*
{

View File

@ -200,10 +200,7 @@ export const createSlug = (text, base = "untitled") => {
return base;
}
text = text
.toString()
.toLowerCase()
.trim();
text = text.toString().toLowerCase().trim();
const sets = [
{ to: "a", from: "[ÀÁÂÃÅÆĀĂĄẠẢẤẦẨẪẬẮẰẲẴẶ]" },

View File

@ -2,6 +2,7 @@ import * as Strings from "~/common/strings";
const USERNAME_REGEX = new RegExp("^[a-zA-Z0-9_]{0,}[a-zA-Z]+[0-9]*$");
const MIN_PASSWORD_LENGTH = 8;
const EMAIL_REGEX = /^[\w-]+@[a-zA-Z0-9_]+?\.[a-zA-Z]{2,50}$/;
// TODO(jim): Regex should cover some of this.
const REJECT_LIST = [
@ -71,6 +72,17 @@ export const slatename = (text) => {
return true;
};
export const email = (text) => {
if (!text || !text.length) {
return false;
}
if (!EMAIL_REGEX.test(text)) {
console.log(text);
return false;
}
return true;
};
export const username = (text) => {
if (Strings.isEmpty(text)) {
return false;

View File

@ -41,6 +41,7 @@ import SidebarAddFileToBucket from "~/components/sidebars/SidebarAddFileToBucket
import SidebarDragDropNotice from "~/components/sidebars/SidebarDragDropNotice";
import SidebarSingleSlateSettings from "~/components/sidebars/SidebarSingleSlateSettings";
import SidebarFilecoinArchive from "~/components/sidebars/SidebarFilecoinArchive";
import SidebarHelp from "~/components/sidebars/SidebarHelp";
// NOTE(jim):
// Core components to the application structure.
@ -65,6 +66,7 @@ const SIDEBARS = {
SIDEBAR_CREATE_SLATE: <SidebarCreateSlate />,
SIDEBAR_DRAG_DROP_NOTICE: <SidebarDragDropNotice />,
SIDEBAR_SINGLE_SLATE_SETTINGS: <SidebarSingleSlateSettings />,
SIDEBAR_HELP: <SidebarHelp />,
};
const SCENES = {
@ -329,10 +331,6 @@ export default class ApplicationPage extends React.Component {
return response;
};
_handleCancel = () => {
this._handleDismissSidebar();
};
_handleDeleteYourself = async () => {
// TODO(jim): Put this somewhere better for messages.
const message =
@ -623,7 +621,7 @@ export default class ApplicationPage extends React.Component {
sidebarLoading: this.state.sidebarLoading,
onSelectedChange: this._handleSelectedChange,
onSubmit: this._handleSubmit,
onCancel: this._handleCancel,
onCancel: this._handleDismissSidebar,
onRegisterFileLoading: this._handleRegisterFileLoading,
onUploadFile: this._handleUploadFile,
onSidebarLoading: this._handleSidebarLoading,

View File

@ -44,7 +44,7 @@ const STYLES_APPLICATION_HEADER = css`
const STYLES_LEFT = css`
flex-shrink: 0;
width: 352px;
${"" /* width: 352px; */}
display: flex;
align-items: center;
justify-content: flex-start;
@ -57,11 +57,12 @@ const STYLES_MIDDLE = css`
`;
const STYLES_RIGHT = css`
flex-shrink: 0;
min-width: 10%;
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 16px;
padding-right: 264px;
`;
const rotate = keyframes`
@ -154,8 +155,17 @@ export default class ApplicationHeader extends React.Component {
<SVG.Search height="20px" />
</span>
</div>
<div css={STYLES_MIDDLE} />
<div css={STYLES_RIGHT} />
{/* <div css={STYLES_MIDDLE} /> */}
<div css={STYLES_RIGHT}>
<span
css={STYLES_ICON_ELEMENT}
onClick={() =>
this.props.onAction({ type: "SIDEBAR", value: "SIDEBAR_HELP" })
}
>
<SVG.Help height="24px" />
</span>
</div>
</header>
);
}

View File

@ -22,7 +22,7 @@ const STYLES_GROUP = css`
const STYLES_HEADER = css`
font-family: ${Constants.font.semiBold};
font-size: 18px;
margin-top: 48px;
margin-top: 32px;
`;
export default class SidebarCreateSlate extends React.Component {
@ -114,14 +114,7 @@ export default class SidebarCreateSlate extends React.Component {
Create slate
</System.P>
<System.P
style={{
fontFamily: Constants.font.semiBold,
fontSize: "1.1rem",
}}
>
Name
</System.P>
<System.P css={STYLES_HEADER}>Name</System.P>
<System.Input
name="name"
style={{ marginTop: 12 }}

View File

@ -0,0 +1,173 @@
import * as React from "react";
import * as Strings from "~/common/strings";
import * as Constants from "~/common/constants";
import * as System from "~/components/system";
import * as Validations from "~/common/validations";
import * as Actions from "~/common/actions";
import { dispatchCustomEvent } from "~/common/custom-events";
import { css } from "@emotion/react";
const STYLES_HEADER = css`
font-family: ${Constants.font.semiBold};
font-size: 18px;
margin-top: 32px;
`;
export default class SidebarCreateSlate extends React.Component {
state = {
name:
this.props.viewer.data && this.props.viewer.data.name
? this.props.viewer.data.name
: "",
email: "",
message: "",
loading: false,
};
_handleSubmit = async () => {
this.setState({ loading: true });
if (!this.state.email || !this.state.email.length) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message: "Please provide an email address where we can reach you",
},
},
});
this.setState({ loading: false });
return;
}
if (!this.state.message || !this.state.message.length) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message: "Please provide a message",
},
},
});
this.setState({ loading: false });
return;
}
if (!Validations.email(this.state.email)) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: { message: "Please check that your email address is valid" },
},
});
this.setState({ loading: false });
return;
}
const response = await Actions.createSupportMessage({
username: this.props.viewer.username,
name: this.state.name,
email: this.state.email,
message: this.state.message,
stored: Strings.bytesToSize(this.props.viewer.stats.bytes),
});
if (!response) {
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message:
"We're having trouble sending out your message right now. Please try again",
},
},
});
this.setState({ loading: false });
return;
}
if (response.error) {
dispatchCustomEvent({
name: "create-alert",
detail: {
decorator: response.decorator,
},
});
this.setState({ loading: false });
return;
}
dispatchCustomEvent({
name: "create-alert",
detail: {
alert: {
message: "Message sent. You'll hear from us shortly",
status: "INFO",
},
},
});
this.setState({ loading: false });
this.props.onCancel();
return;
};
_handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<div>
<System.P
style={{
fontFamily: Constants.font.semiBold,
fontSize: Constants.typescale.lvl3,
marginBottom: "64px",
}}
>
Talk to us
</System.P>
<System.P css={STYLES_HEADER}>Name</System.P>
<System.Input
name="name"
style={{ marginTop: 16 }}
placeholder="Name"
value={this.state.name}
onChange={this._handleChange}
onSubmit={this._handleSubmit}
/>
<System.P css={STYLES_HEADER}>Email</System.P>
<System.Input
name="email"
style={{ marginTop: 16 }}
placeholder="Email"
value={this.state.email}
onChange={this._handleChange}
onSubmit={this._handleSubmit}
/>
<System.P css={STYLES_HEADER}>Message</System.P>
<System.Textarea
style={{ marginTop: 16 }}
name="message"
value={this.state.message}
placeholder="Leave us your questions or feedback and we'll get back to you soon!"
onChange={this._handleChange}
onSubmit={this._handleSubmit}
/>
<System.ButtonPrimary
full
style={{ marginTop: 48 }}
onClick={this._handleSubmit}
loading={this.state.loading}
>
Send message
</System.ButtonPrimary>
</div>
);
}
}

View File

@ -21,7 +21,7 @@ const STYLES_GROUP = css`
const STYLES_HEADER = css`
font-family: ${Constants.font.semiBold};
font-size: 18px;
margin-top: 48px;
margin-top: 32px;
`;
export default class SidebarSingleSlateSettings extends React.Component {
@ -168,7 +168,6 @@ export default class SidebarSingleSlateSettings extends React.Component {
style={{ marginTop: 12 }}
name="body"
value={this.state.body}
placeholder="A slate."
onChange={this._handleChange}
onSubmit={this._handleSubmit}
/>

View File

@ -24,7 +24,23 @@ const STYLES_TEXTAREA = css`
border: 0;
transition: 200ms ease all;
padding: 16px 24px 16px 24px;
box-shadow: inset 0 0 0 1px ${Constants.system.border};
box-shadow: 0 0 0 1px ${Constants.system.border} inset;
::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: ${Constants.system.darkGray};
opacity: 1; /* Firefox */
}
:-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: ${Constants.system.darkGray};
}
::-ms-input-placeholder {
/* Microsoft Edge */
color: ${Constants.system.darkGray};
}
`;
export class Textarea extends React.Component {
@ -34,6 +50,7 @@ export class Textarea extends React.Component {
css={STYLES_TEXTAREA}
style={this.props.style}
onChange={this.props.onChange}
placeholder={this.props.placeholder}
name={this.props.name}
value={this.props.value}
readOnly={this.props.readOnly}

31
node_common/support.js Normal file
View File

@ -0,0 +1,31 @@
import * as Environment from "~/node_common/environment";
import * as Strings from "~/common/strings";
import { IncomingWebhook } from "@slack/webhook";
const url = `https://hooks.slack.com/services/${Environment.SUPPORT_SLACK_WEBHOOK_KEY}`;
const webhook = new IncomingWebhook(url);
export const sendSlackMessage = ({
username,
name,
email,
message,
stored,
}) => {
if (Strings.isEmpty(Environment.SUPPORT_SLACK_WEBHOOK_KEY)) {
return false;
}
const userProfileURL = `https://slate.host/${username}`;
const userURL = `<${userProfileURL}|${username}>`;
try {
webhook.send({
text: `\n*Username:* ${userURL} (${stored} stored)\n*Name:* ${name}\n*Email:* ${email}\n*Message:* ${message}`,
});
return true;
} catch (e) {
return false;
}
};

View File

@ -0,0 +1,69 @@
import * as Environment from "~/node_common/environment";
import * as Data from "~/node_common/data";
import * as Utilities from "~/node_common/utilities";
import * as Serializers from "~/node_common/serializers";
import * as Support from "~/node_common/support";
export default async (req, res) => {
const id = Utilities.getIdFromCookie(req);
if (!id) {
return res.status(500).send({ decorator: "SERVER_SUPPORT", error: true });
}
const user = await Data.getUserById({
id,
});
if (!user) {
return res
.status(404)
.send({ decorator: "SERVER_SUPPORT_USER_NOT_FOUND", error: true });
}
if (user.error) {
return res
.status(500)
.send({ decorator: "SERVER_SUPPORT_USER_NOT_FOUND", error: true });
}
if (!req.body.data) {
return res.status(500).send({
decorator: "SERVER_SUPPORT_NO_DATA_PROVIDED",
error: true,
});
}
if (!req.body.data.email) {
return res.status(500).send({
decorator: "SERVER_SUPPORT_MUST_PROVIDE_EMAIL",
error: true,
});
}
if (!req.body.data.message) {
return res.status(500).send({
decorator: "SERVER_SUPPORT_MUST_PROVIDE_MESSAGE",
error: true,
});
}
if (!req.body.data.username) {
return res.status(500).send({
decorator: "SERVER_SUPPORT_NO_DATA_PROVIDED",
error: true,
});
}
let status = await Support.sendSlackMessage(req.body.data);
if (status) {
return res.status(200).send({
decorator: "SERVER_SUPPORT",
data: true,
});
} else {
return res.status(500).send({
decorator: "SERVER_SUPPORT",
error: true,
});
}
};

View File

@ -71,7 +71,7 @@ export default class SceneFilesFolder extends React.Component {
_interval;
state = {
view: "list",
view: "grid",
startIndex: 0,
};