Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ yarn-error.log*
.idea/
src/SPREADSHEET_SECRETS.js
.firebase/
functions/.runtimeconfig.json
public/precache-manifest.*

# Project management (internal)
Expand Down
61 changes: 61 additions & 0 deletions functions/funcSendHelpRequest/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const sgMail = require("@sendgrid/mail");
const functions = require("firebase-functions");
const {getUser} = require("../common/accessUtils");

sgMail.setApiKey(functions.config().sendgrid ? functions.config().sendgrid.key : "");

const HELP_RECIPIENT = "contact@chalkcoaching.com";
const SENDER_ADDRESS = "chalkcoaching@gmail.com";

exports.funcSendHelpRequest = functions.https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError(
"unauthenticated",
"User must be logged in to submit a help request."
);
}

const {message} = data;
if (!message || message.trim().length === 0) {
throw new functions.https.HttpsError(
"invalid-argument",
"Message is required."
);
}

const userData = await getUser(context.auth.uid);
const userName = `${userData.firstName} ${userData.lastName}`;
const userEmail = userData.email;
const userRole = userData.role;

const emailMessage = {
to: HELP_RECIPIENT,
replyTo: userEmail,
from: SENDER_ADDRESS,
subject: `CHALK Help Request from ${userName}`,
text: [
`Help request from: ${userName}`,
`Email: ${userEmail}`,
`Role: ${userRole}`,
``,
`Message:`,
message,
``,
`---`,
`Sent from CHALK Coaching`
].join("\n")
};

return sgMail.send(emailMessage)
.then(() => {
console.log("Help request email sent");
return {success: true};
})
.catch((err) => {
console.error("Error sending help request:", JSON.stringify(err));
throw new functions.https.HttpsError(
"internal",
"Failed to send help request. Please try again."
);
});
});
1 change: 1 addition & 0 deletions functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ exports.funcLiteracyTrendLanguage = require('./funcLiteracyTrendLanguage').funcL
exports.funcMathDetails = require('./funcMathDetails').funcMathDetails;
exports.funcRecentObservations = require('./funcRecentObservations').funcRecentObservations;
exports.funcSendEmail = require('./funcSendEmail').funcSendEmail;
exports.funcSendHelpRequest = require('./funcSendHelpRequest').funcSendHelpRequest;
exports.funcSendMLE = require('./funcSendMLE').funcSendMLE;
exports.funcSeqDetails = require('./funcSeqDetails').funcSeqDetails;
exports.funcSessionDates = require('./funcSessionDates').funcSessionDates;
Expand Down
19 changes: 12 additions & 7 deletions src/components/BurgerMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import ReactRouterPropTypes from 'react-router-prop-types';
import * as Types from '../constants/Types';
import * as H from 'history';
import Firebase from './Firebase';
import HelpRequestDialog from './Shared/HelpRequestDialog';
import SupervisedUserCircleIcon from '@material-ui/icons/SupervisedUserCircle';

const styles: object = {
Expand Down Expand Up @@ -75,7 +76,8 @@ interface State {
chalkOpen: boolean,
teacherModal: boolean,
practiceOpen: boolean,
type: string
type: string,
helpDialogOpen: boolean
}

/**
Expand All @@ -91,7 +93,8 @@ class BurgerMenu extends React.Component<Props, State>{
chalkOpen: false,
teacherModal: false,
practiceOpen: false,
type: ""
type: "",
helpDialogOpen: false
};
handleDrawerOpen = (): void => {
this.setState({ open: true });
Expand Down Expand Up @@ -459,11 +462,9 @@ class BurgerMenu extends React.Component<Props, State>{
</ListItem>
<ListItem
button
disabled
onClick={() => this.props.handleNavigation( (): void => {
this.setState({ menu: 13, chalkOpen: false });
this.props.history.push("/help");
})}
onClick={(): void => {
this.setState({ helpDialogOpen: true });
}}
>
<ListItemIcon>
<HelpIcon style={{ fill: Constants.Colors.TT }} />
Expand Down Expand Up @@ -536,6 +537,10 @@ class BurgerMenu extends React.Component<Props, State>{
) : (
<div />
)}
<HelpRequestDialog
open={this.state.helpDialogOpen}
handleClose={(): void => this.setState({ helpDialogOpen: false })}
/>
</div>
);
}
Expand Down
15 changes: 15 additions & 0 deletions src/components/Firebase/Firebase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,21 @@ class Firebase {
.catch(error => error)
}

sendHelpRequest = async (message: string): Promise<{success: boolean}> => {
const sendHelpRequestFunction = this.functions.httpsCallable(
'funcSendHelpRequest'
)
return sendHelpRequestFunction({message})
.then(result => {
console.log('Help request sent:', result)
return {success: true}
})
.catch(error => {
console.error('Error sending help request:', error)
throw error
})
}

/**
* gets list of all teachers linked to current user's account
*/
Expand Down
159 changes: 159 additions & 0 deletions src/components/Shared/HelpRequestDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import { TextField, CircularProgress } from '@material-ui/core';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
import Firebase, { FirebaseContext } from '../Firebase';

interface Props {
open: boolean,
handleClose(): void,
}

interface State {
message: string,
sending: boolean,
sent: boolean,
error: string
}

class HelpRequestDialog extends React.Component<Props, State> {
static contextType = FirebaseContext;
context!: Firebase;

constructor(props: Props) {
super(props);
this.state = {
message: '',
sending: false,
sent: false,
error: ''
};
}

static propTypes = {
open: PropTypes.bool.isRequired,
handleClose: PropTypes.func.isRequired
}

handleSubmit = async (): Promise<void> => {
const { message } = this.state;
if (!message.trim()) {
this.setState({ error: 'Please enter a message.' });
return;
}

this.setState({ sending: true, error: '' });

try {
await this.context.sendHelpRequest(message.trim());
this.setState({ sending: false, sent: true });
} catch (error) {
console.error('Error sending help request:', error);
this.setState({
sending: false,
error: 'There was a problem sending your request. Please try again.'
});
}
}

handleClose = (): void => {
this.setState({
message: '',
sending: false,
sent: false,
error: ''
});
this.props.handleClose();
}

render(): React.ReactNode {
const { open } = this.props;
const { message, sending, sent, error } = this.state;

return (
<Dialog
open={open}
onClose={this.handleClose}
aria-labelledby="help-request-dialog-title"
maxWidth="sm"
fullWidth
PaperProps={{ style: { border: '2px solid #459aeb', borderRadius: '8px' } }}
>
{sent ? (
<>
<DialogContent style={{ textAlign: 'center', padding: '2em' }}>
<CheckCircleIcon style={{ fontSize: 48, color: '#009365', marginBottom: '0.5em' }} />
<Typography variant="h6" style={{ fontFamily: 'Arimo', marginBottom: '0.5em' }}>
Message Received!
</Typography>
<DialogContentText style={{ fontFamily: 'Arimo' }}>
Thank you for reaching out. A member of our team will respond within 2 business days.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color="primary" style={{ fontFamily: 'Arimo' }}>
Close
</Button>
</DialogActions>
</>
) : (
<>
<DialogTitle id="help-request-dialog-title" style={{ fontFamily: 'Arimo' }}>
Help & Support
</DialogTitle>
<DialogContent>
<DialogContentText style={{ fontFamily: 'Arimo' }}>
How can we help? Describe your question or issue below and our team will get back to you within 2 business days.
</DialogContentText>
<TextField
autoFocus
margin="dense"
id="help-message"
label="Your message"
type="text"
fullWidth
multiline
rows={4}
variant="outlined"
value={message}
onChange={(e): void => this.setState({ message: e.target.value, error: '' })}
error={!!error}
helperText={error}
disabled={sending}
inputProps={{ style: { fontFamily: 'Arimo' } }}
InputLabelProps={{ style: { fontFamily: 'Arimo' } }}
/>
</DialogContent>
<DialogActions>
<Button
onClick={this.handleClose}
color="primary"
style={{ fontFamily: 'Arimo' }}
disabled={sending}
>
Cancel
</Button>
<Button
onClick={this.handleSubmit}
color="primary"
style={{ fontFamily: 'Arimo' }}
disabled={sending || !message.trim()}
>
{sending ? <CircularProgress size={24} /> : 'Submit'}
</Button>
</DialogActions>
</>
)}
</Dialog>
);
}
}

export default HelpRequestDialog;
15 changes: 12 additions & 3 deletions src/views/protected/HomeViews/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import * as Types from '../../../constants/Types';
import ReactRouterPropTypes from 'react-router-prop-types';
import * as H from 'history';
import Firebase from '../../../components/Firebase'
import HelpRequestDialog from '../../../components/Shared/HelpRequestDialog'

const styles: object = {
root: {
Expand Down Expand Up @@ -99,7 +100,8 @@ interface Props {
interface State {
teacherModal: boolean,
type: string,
coachName: string
coachName: string,
helpDialogOpen: boolean
}

/**
Expand All @@ -116,7 +118,8 @@ class HomePage extends React.Component<Props, State> {
this.state = {
teacherModal: false,
type: "",
coachName: ""
coachName: "",
helpDialogOpen: false
}
}

Expand Down Expand Up @@ -546,7 +549,9 @@ class HomePage extends React.Component<Props, State> {
</Grid>
<Grid item xs={5}>
<Grid container direction="row" justify="flex-start" alignItems="center">
<Button color="primary" className={classes.helpButtons} style={{paddingLeft: '2em'}}>
<Button color="primary" className={classes.helpButtons} style={{paddingLeft: '2em'}}
onClick={(): void => this.setState({ helpDialogOpen: true })}
>
HELP
</Button>
</Grid>
Expand Down Expand Up @@ -581,6 +586,10 @@ class HomePage extends React.Component<Props, State> {
) : (
<div />
)}
<HelpRequestDialog
open={this.state.helpDialogOpen}
handleClose={(): void => this.setState({ helpDialogOpen: false })}
/>
</div>
);
}
Expand Down
Loading
Loading