VIP web application
by Hancheng Zhao

UD version

User Story
Roles
Admin
Advisor
Student
Sections
Announcements
Projects
Peer Review
Dashboard
User Story
Roles
Admin
Advisor
Student
Sections
Announcements
Projects
Peer Review
Dashboard
CRUD
View
View
User Story
Roles
Admin
Advisor
Student
Sections
Announcements
Projects
Peer Review
Dashboard
CRUD
CRUD own
Apply
Email Service
User Story
Roles
Admin
Advisor
Student
Sections
Announcements
Projects
Peer Review
Dashboard
Generate & Check
Generate & Check own
Review
User Story
Roles
Admin
Advisor
Student
Sections
Announcements
Projects
Peer Review
Dashboard
Roster/Application/Semester
Roster/Application
Team info

Structure
React.js
- Mainly view layer
- Component-Based
- Virtual dom & JSX
- CLI tooling
React.js
- UI = f(state)
- Declarative
class App extends Component {
constructor () {
this.state = {
loading: true
}
}
...
render() {
return (
<div>
<Header />
{this.state.loading === true
? (<h2>Loading...</h2>)
: (
<div className="App">
<Route exact path="/" component={Home}/>
<Route path="/announcement" component={Announcement}/>
<Route path="/projects" component={Projects}/>
<Route path="/peer-review" component={PeerReview}/>
</div>
)}
<Footer />
</div>
);
}
}
export default App;React.js
Componet-Based
class App extends Component {
constructor () {
this.state = {
loading: true
}
}
...
render() {
return (
<div>
<Header />
{this.state.loading === true
? (<h2>Loading...</h2>)
: (
<div className="App">
<Route exact path="/" component={Home}/>
<Route path="/announcement" component={Announcement}/>
<Route path="/projects" component={Projects}/>
<Route path="/peer-review" component={PeerReview}/>
</div>
)}
<Footer />
</div>
);
}
}
export default App;class Footer extends Component {
render() {
return (
<div className="container">
<div className="footer">
<div id="innovation-footer">
<div id="innovation-bar">
<div className="innovation-top">
<div className="innovation-status">
<a href="https://yourfuture.asu.edu/rankings"><span>ASU is #1 in the U.S. for Innovation</span></a>
</div>
<div className="innovation-hidden">
<a href="https://yourfuture.asu.edu/rankings"><img src="//www.asu.edu/asuthemes/4.6/assets/best-college-2017.png" alt="Best Colleges U.S. News Most Innovative 2017"/></a>
</div>
</div>
</div>
<div className="footer-menu">
<ul className="default">
<li className="links-footer"><a href="http://www.asu.edu/copyright/">Copyright & Trademark</a></li>
<li className="links-footer"><a href="http://www.asu.edu/accessibility/">Accessibility</a></li>
<li className="links-footer"><a href="http://www.asu.edu/privacy/">Privacy</a></li>
<li className="links-footer"><a href="http://www.asu.edu/asujobs">Jobs</a></li>
<li className="links-footer"><a href="http://www.asu.edu/emergency/">Emergency</a></li>
<li className="no-border links-footer"><a href="http://www.asu.edu/contactasu/">Contact ASU</a></li>
</ul>
</div>
</div>
</div>
</div>
);
}
}React.js
Componet-Based

React.js
Componet-Based

QuestionCard
React.js
Componet-Based

QuestionContainer
React.js
Componet-Based

QuestionContainer
{questions.map((question, i) => (
<QuestionCard
key={question.id}
index={i}
id={question.id}
type={question.type}
data={question.data}
moveQuestion={this.moveQuestion}
removeQuestion={this.removeQuestion}
updateQuestion={this.updateQuestion}
/>
))}React.js
Componet-Based

QuestionContainer

React.js
- CLI tooling
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js --env=jsdom",
"deploy": "cp -r ./build/. ../firebase/public && cd ../firebase/ && firebase deploy --only hosting"
}Mobx
1. Define your state and make it observable
2. Create a view that responds to changes in the State
3. Modify the State
Mobx
state container
decorator
import { observable } from "mobx";
class UserStore {
@observable authed = false;
login() {
this.authed = true;
}
logout() {
this.authed = false;
}
}
const userStore = new UserStore();
export default userStore;Mobx
import { observer } from "mobx-react";
@observer
class App extends Component {
constructor () {
...
}
componentDidMount () {
this.userStateChange = firebase.auth().onAuthStateChanged((user) => {
if (user) {
userStore.login()
...
}).catch((noRole) => { //unable to retrieve role from db
userStore.fetchUserRole(noRole);
})
} else {
userStore.logout()
}
})
}
...
}React router
- also component-based
export const AdminRoute = ({component: Component, user, ...rest}) => (
<Route {...rest}
render={(props) => user.role === "admin"
? <Component {...props} />
: <Redirect to='/login'/>}
/>
)
export const PrivateRoute = ({component: Component, authed, ...rest}) => (
<Route {...rest}
render={(props) => authed === true
? <Component {...props} />
: <Redirect to={{pathname: '/login', state: {from: props.location}}} />}
/>
)
React router
- dynamic
const Announcement = ( {match} ) => (
<div>
<Switch>
<AdminRoute exact path={`${match.url}/creation`} user={userStore} component={ AnnouncementCreate }/>
<Route exact path={`${match.url}/edit/:announcementId`} user={userStore} component={ AnnouncementEdit }/>
<Route path={`${match.url}/:announcementId`} component={ AnnouncementPage }/>
<Route exact path={match.url} component={ AnnouncementList }/>
</Switch>
</div>
)
export default Announcement;
Firebase
- OAuth
- Database
- Storage
- Hosting
Firebase
- OAuth
googleLogin(user) {
let provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithRedirect(provider);
firebase.auth().getRedirectResult(provider).then((result) => {
//...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log("errorMessage: " + errorMessage);
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
})
}Firebase
- Database
const currentTime = new Date().getTime();
let deleteOverDue = admin.database().ref().child(Announcement_Path)
.orderByChild('endDate')
.endAt(currentTime)
.once("value")
.then(snap => {
sunset = snap.val();
console.log(sunset);
if (sunset) {
return admin.database().ref()
.child(Announcement_Sunset)
.update(sunset);
}
})
Firebase
- Storage
const ref = firebase.storage().ref()
let photoRef = ref.child("announcement_photos/" + newdt + "/" + photo.name);
photoRef.put(photo).then((snap) => {
this.setState(prevState => ({
content : prevState.content + `\n\n<img alt="${photo.name}" src="${snap.downloadURL}" width="50%">`
}))
})Firebase
- Hosting:
- npm build
- npm deploy
Cloud functions
-
Cloud Firestore Triggers
-
Realtime Database Triggers
-
Firebase Authentication Triggers
-
Google Analytics for Firebase Triggers
-
Crashlytics Triggers
-
Cloud Storage Triggers
-
Cloud Pub/Sub Triggers
-
HTTP Triggers
Cloud functions
Cron-job

Cloud functions
functions.https.onRequest((req, res) => {
const key = req.query.key;
// Exit if the keys don't match
if (!secureCompare(key, functions.config().cron.key)) {
console.log('The key provided in the request does not match the key set in the environment. Check that', key,
'matches the cron.key attribute in `firebase env:get`');
res.status(403).send('Security key does not match. Make sure your "key" URL query parameter matches the ' +
'cron.key environment variable.');
return;
}
const currentTime = new Date().getTime();
let newAnnouncement;
let sunset;
//check startDate in raw data
let addWaitedList = admin.database().ref().child(Announcement_Raw_Data).orderByChild('startDate').endAt(currentTime).once("value")
.then(snap => {
newAnnouncement = snap.val();
console.log(newAnnouncement);
if (newAnnouncement) {
return admin.database().ref().child(Announcement_Path).update(newAnnouncement)
}
}).then(() => {
let update = {}
Object.keys(newAnnouncement).forEach((uuid) => {
update[uuid] = null;
});
console.log(Object.keys(newAnnouncement).length + " announcements have been updated");
admin.database().ref(Announcement_Raw_Data).update(update);
}).catch(err => {
res.send(err)
})
//check endDate in announcements
let deleteOverDue = admin.database().ref().child(Announcement_Path).orderByChild('endDate').endAt(currentTime).once("value")
.then(snap => {
sunset = snap.val()
console.log(sunset)
if (sunset) {
return admin.database().ref().child(Announcement_Sunset).update(sunset)
}
}).then(() => {
if (sunset) {
let update = {}
Object.keys(sunset).forEach((uuid) => {
update[uuid] = null;
});
console.log(Object.keys(sunset).length + " announcements have been sunset");
return admin.database().ref().child(Announcement_Path).update(update);
} else {
console.log("No announcements need to be sunset")
}
}).catch(err => {
res.send(err)
})
Promise.all([addWaitedList, deleteOverDue]).then(value => {
console.log(`Cron job for ${new Date()} has been completed`)
});
})Sendgrid
- Easy-to-integrate APIs
- Transaction templates
- Version control
Sendgrid
Hello -name-,
A new team application was submitted, please go to the dashboard to checkout.
<%body%>
VIP Email Service
Template
Sendgrid
Object.keys(adminLists).forEach((uuid) => {
let request = sg.emptyRequest({
method: 'POST',
path: '/v3/mail/send',
body: {
personalizations: [{
to: [{ email: adminLists[uuid].email }],
'substitutions': {
'-name-': adminLists[uuid].name,
},
subject: 'A new team application is submitted'
}],
from: {
email: 'noreply@vip.udel.edu'
},
content: [{
type: 'text/html',
value: `<p>Applicaiton information:</p> ${formatted.join("")}`
}],
'template_id': functions.config().sendgrid.templateid,
}
});
// With promise
sg.API(request)
.then(function(response) {
console.log(response.statusCode);
console.log(response.body);
console.log(response.headers);
})
.catch(function(error) {
// error is an instance of SendGridError
// The full response is attached to error.response
console.log(error.response.statusCode);
});
});API
VIP Web
By Henry Zhao
VIP Web
- 371