diff --git a/.gitignore b/.gitignore index 3b536d2..fa1ecd2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.next firebasekeys.json +cloudinarykeys.json \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index ee41b74..d421234 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,5 +4,5 @@ "semi": true, "singleQuote": true, "proseWrap": "preserve", - "printWidth": 120 + "printWidth": 100 } diff --git a/README.md b/README.md index dc87b3f..e281202 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # Sage Prosthetics -## About +## About -This repo contains the source code for the Sage Prosthetics website. Sage Prosthetics is a service group at Sage Hill School that provides 3D printed prosthetic hands and arms for underpriveleged children and adults. - -Not deployed yet +This repo contains the source code for the Sage Prosthetics website. Sage Prosthetics is a service group at Sage Hill School that provides 3D printed prosthetic hands and arms for underpriveleged children and adults. +https://www.sageprosthetics.org ## Usage @@ -13,10 +12,12 @@ Email me for the firebase keys Clone the project -### Terminal commands: +### Terminal commands: + ``` yarn ``` + ``` yarn run dev ``` diff --git a/components/BioModal.js b/components/BioModal.js new file mode 100644 index 0000000..61ff930 --- /dev/null +++ b/components/BioModal.js @@ -0,0 +1,102 @@ +import React from 'react'; +import { Image, Transformation } from 'cloudinary-react'; +import anime from 'animejs'; +import Transition from 'react-transition-group/Transition'; + +import Person from './Person'; + +const BioModal = props => { + if (props.show) { + return ( + +
+ +
+ +
event.preventDefault()} + className="biomodal" + > + +
+    + {props.person.bio}{' '} +
+
+
+
+ + ); + } + + return null; +}; + +const modalEnter = biomodal => { + console.log('in'); + return anime({ + // targets: biomodal, + // opacity: { + // value: [0, 1] + // }, + // easing: 'easeOutQuint', + // duration: 1000 + }); +}; + +const modalExit = biomodal => { + console.log('out'); + return anime({ + // targets: biomodal, + // opacity: { + // value: [1, 0] + // }, + // easing: 'easeOutQuint', + // duration: 1000 + }); +}; + +export default BioModal; diff --git a/components/CloudinaryInput.js b/components/CloudinaryInput.js new file mode 100644 index 0000000..4410a0d --- /dev/null +++ b/components/CloudinaryInput.js @@ -0,0 +1,39 @@ +import React, { Component } from 'react'; +import Button from 'grommet/components/Button'; +import cloudinaryKeys from '../cloudinarykeys.json'; + +class CloudinaryInput extends Component { + static defaultProps = { + label: 'Upload Image to Cloudinary' + }; + + processCloudinaryResult = (error, results) => { + if (results) { + const result = results[0]; + const { secure_url, public_id, path } = result; + this.props.onUploadSuccess({ url: secure_url, id: path, public_id }); + } + }; + + openCloudinaryUploader = () => { + const { cloud_name, upload_preset } = cloudinaryKeys; + cloudinary.openUploadWidget( + { cloud_name, upload_preset: upload_preset }, + this.processCloudinaryResult + ); + }; + + render() { + return ( +
+
+ ); + } +} + +export default CloudinaryInput; diff --git a/components/ConfirmModal.js b/components/ConfirmModal.js new file mode 100644 index 0000000..a45cbb0 --- /dev/null +++ b/components/ConfirmModal.js @@ -0,0 +1,82 @@ +import React from 'react'; +import Button from 'grommet/components/Button'; + +const ConfirmModal = props => { + if (props.show) { + return ( + +
+ +
+
event.preventDefault()} + > +

Warning!

+
{props.message}
+
+
+
+
+ + ); + } + + return null; +}; + +export default ConfirmModal; diff --git a/components/Header.js b/components/Header.js new file mode 100644 index 0000000..d3dfe24 --- /dev/null +++ b/components/Header.js @@ -0,0 +1,331 @@ +import React, { Component } from 'react'; +import Box from 'grommet/components/Box'; +import Header from 'grommet/components/Header'; +import Popover from 'react-simple-popover'; +import Title from 'grommet/components/Title'; +import Link from 'next/link'; + +class DesktopHeader extends Component { + state = { + dropdown: '', + secondDropdown: '' + }; + + render() { + const { archive, links } = this.props.recipients; + let archiveactive = false; + + if (archive) { + archive.forEach(recipient => { + if (recipient === this.props.page) { + archiveactive = true; + } + }); + } + + let linksactive = archiveactive; + if (links) { + links.forEach(recipient => { + if (recipient === this.props.page) { + linksactive = true; + } + }); + } + + let projectsactive = false; + this.props.projects.forEach(project => { + if (project === this.props.page) { + projectsactive = true; + } + }); + + return ( +
+ + <a style={{ fontSize: '30px', margin: 0 }} href="/"> + <img + src="/static/biglogo.png" + alt="logo" + style={{ height: '5vw', marginBottom: 0 }} + /> + </a> + + + + + +
+ ); + } +} + +const navlinks = [ + { text: 'Prosthetic Designs', link: '/prosthetic-designs', page: 'h' }, + { text: 'Gallery', link: '/gallery', page: 'g' }, + { text: 'Our Group', link: '/group', page: 't' }, + { text: 'In the News', link: '/news', page: 'n' }, + { text: 'Contact', link: '/contact', page: 'c' } +]; + +const styles = { + navlink: { + margin: '6px 25px 0px 0px', + fontWeight: '500' + //color: "#CCCCCC" + }, + navBar: { + borderWidth: '0px', + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + margin: '10px' + }, + search: { + borderWidth: '0px', + width: '20vw', + marginRight: '3vw', + height: '40px', + marginTop: '10px', + fontSize: '1.2vw' + } +}; + +export default DesktopHeader; diff --git a/components/ImageModal.js b/components/ImageModal.js index f99eb02..3bb80ef 100644 --- a/components/ImageModal.js +++ b/components/ImageModal.js @@ -37,8 +37,8 @@ const ImageModal = props => { { + return { + text: recipient, + link: `/recipient/${recipient}`, + page: recipient + }; + }); + } + this.links.push({ text: 'Archive', link: '#', page: '', onClick: 3 }); + if (this.props.recipients.archive) { + this.archive = this.props.recipients.archive.map(recipient => { + return { + text: recipient, + link: `/recipient/${recipient}`, + page: recipient + }; + }); + } + if (this.props.projects) { + this.projects = this.props.projects.map(project => { + return { + text: project, + link: `/project/${project}`, + page: project + }; + }); + } + } + + renderPanel() { + let iterate = navlinks; + if (this.state.show === 2) { + iterate = this.links; + } else if (this.state.show === 3) { + iterate = this.archive; + } else if (this.state.show === 4) { + iterate = this.projects; + } + return ( +
+ {iterate.map(object => { + return ( + + this.setState({ show: object.onClick }) + : () => this.setState({ show: 0 }) + } + > +
+ {object.text} +
+
+ + ); + })} + this.setState({ show: transform[this.state.show] })} + > +
+ +
Go Back
+
+
+
+ ); + } + + render() { + return ( +
+
+
+ + logo + + this.setState({ show: this.state.show === 0 ? 1 : 0 })} + colorIndex="brand" + style={{ marginRight: '30px' }} + /> +
+ {this.state.show !== 0 ? this.renderPanel() : null} +
+
+ ); + } +} + +const transform = [0, 0, 1, 1, 2]; + +const navlinks = [ + { text: 'Recipients', link: '#', page: '', onClick: 2 }, + { text: 'Projects', link: '#', page: '', onClick: 4 }, + { text: 'Hand Designs', link: '/hand-designs', page: 'h' }, + { text: 'Gallery', link: '/gallery', page: 'g' }, + { text: 'Our Group', link: '/group', page: 't' }, + { text: 'In the News', link: '/news', page: 'n' }, + { text: 'Contact', link: '/contact', page: 'c' } +]; + +const styles = { + navlink: { + margin: '6px 25px 0px 0px', + fontWeight: '500' + //color: "#CCCCCC" + }, + navBar: { + borderWidth: '0px', + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + margin: '10px' + }, + search: { + borderWidth: '0px', + width: '20vw', + marginRight: '3vw', + height: '40px', + marginTop: '10px', + fontSize: '1.2vw' + } +}; + +export default MobileHeader; diff --git a/components/Person.js b/components/Person.js new file mode 100644 index 0000000..e8d6023 --- /dev/null +++ b/components/Person.js @@ -0,0 +1,54 @@ +import React from 'react'; +import { Image, Transformation } from 'cloudinary-react'; +import Button from 'grommet/components/Button'; + +const Person = props => { + return ( +
+ + + +
+

+ {props.name} +

+ {props.faculty ?
Faculty Advisor
: null} + {props.faculty && !props.horizontal ? ( + + ) : null} +
+
+ ); +}; + +export default Person; diff --git a/database.json b/database.json deleted file mode 100644 index 15d2791..0000000 --- a/database.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "group": [ - {"name": "Aaron Bergen", "src": "IMG_4926-e1510555983904", "id": "aaronbergen"}, - {"name": "Alex Desbans", "src": "FullSizeRender-6", "id": "alexdesbans"} - {"name": "Alex Kwon", "src": "Screen_Shot_2018-11-06_at_10.12.38_PM", "id": "alexkwon"} - ] -} - -1: -2: -3: {name: "Anika Arora", src: "AnikaArora", id: "anikaarora" -} -4: {name: "Ashley Zeng", src: "20180901_192217_HDR", id: "ashleyzeng" -} -5: {name: "Brandon Phan", src: "Screen_Shot_2018-11-06_at_10.16.39_PM", id: "brandonphan" -} -6: {name: "Cammi Phillips", src: "IMG_5538-e1511729257888", id: "cammiphillips" -} -7: {name: "Eamon Niknafs", src: "IMG_0132", id: "eamonniknafs" -} -8: {name: "Emir Karabeg", src: "IMG_4697", id: "emirkarabeg" -} -9: {name: "Ian Grimm", src: "Ian_Grimm", id: "iangrimm" -} -10: {name: "Jackie Ni", src: "UNADJUSTEDNONRAW_thumb_11fb", id: "jackieni" -} -11: {name: "Jenny King", src: "IMG_7681", id: "jennyking" -} -12: {name: "John King", src: "IMG_4130", id: "johnking" -} -13: {name: "Jonathan Stark", src: "IMG_4036-e1512758206108", id: "jonathanstark" -} -14: {name: "Julia Yuen", src: "JuliaYuen", id: "juliayuen" -} -15: {name: "Karishma Raghuram", src: "DSC_6277-e1511729414346", id: "karishmaraghuram" -} -16: {name: "Kelly Hong", src: "Screen_Shot_2018-11-06_at_10.17.52_PM", id: "kellyhong" -} -17: {name: "Lexi Brooks", src: "Screen-Shot-2017-11-16-at-12.53.07-PM", id: "lexibrooks" -} -18: {name: "Sasha Ronaghi", src: "Screen_Shot_2018-11-06_at_10.40.45_PM", id: "sasharonaghi" -} -19: {name: "Timothy Guo", src: "APC_0293", id: "timothyguo" -} -20: {name: "Trinity Cha", src: "Trinitycha", id: "trinitycha" -} -21: {name: "William Stomber", src: "Photo-on-5-18-17-at-5.09-PM", id: "williamstomber" -} \ No newline at end of file diff --git a/devserver.js b/devserver.js deleted file mode 100644 index 2cebc5f..0000000 --- a/devserver.js +++ /dev/null @@ -1,42 +0,0 @@ -const { createServer } = require('http'); -const express = require('express'); - -const next = require('next'); -const admin = require('firebase-admin'); -const serviceAccount = require('./firebasekeys.json'); - -const port = parseInt(process.env.PORT, 10) || 3000; -const dev = process.env.NODE_ENV !== 'production'; -const app = next({ dev }); - -//const handle = app.getRequestHandler(); -const routes = require('./routes'); -const handler = routes.getRequestHandler(app); - -const firebase = admin.initializeApp( - { - credential: admin.credential.cert(serviceAccount), - databaseURL: 'https://sage-prosthetics.firebaseio.com' - }, - 'server' -); - -console.log(process.env.NODE_ENV); - -app.prepare().then(() => { - const server = express(); - - server.use((req, res, next) => { - req.firebaseServer = firebase; - next(); - }); - - server.get('*', (req, res) => { - return handler(req, res); - }); - - server.listen(3001, err => { - if (err) throw err; - console.log('Ready on http://localhost:3001 (with a custom server)'); - }); -}); diff --git a/next.config.js b/next.config.js index f272f80..37bcb51 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,7 @@ const withSass = require('@zeit/next-sass'); module.exports = withSass({ + target: 'serverless', sassLoaderOptions: { includePaths: ['./node_modules'] }, @@ -15,7 +16,3 @@ module.exports = withSass({ }; } }); - -// const withCSS = require('@zeit/next-css'); - -// module.exports = withCSS(); diff --git a/now.json b/now.json new file mode 100644 index 0000000..08dee81 --- /dev/null +++ b/now.json @@ -0,0 +1,4 @@ +{ + "version": 2, + "builds": [{ "src": "next.config.js", "use": "@now/next" }] +} diff --git a/package.json b/package.json index c7ca6e2..592fc00 100755 --- a/package.json +++ b/package.json @@ -4,32 +4,35 @@ "main": "index.js", "license": "MIT", "dependencies": { + "@app-masters/react-cloudinary-uploader": "^0.1.41", + "@now/next": "^0.0.81", "@zeit/next-sass": "^1.0.1", "animejs": "^2.2.0", + "cloudinary": "^1.13.2", "cloudinary-react": "^1.0.6", - "express": "^4.16.4", "firebase": "^5.5.8", - "firebase-admin": "^6.1.0", - "firestore-export-import": "^0.1.6", + "global": "^4.3.2", "grommet": "^1.11.0", "har-validator": "^5.1.3", - "next": "^7.0.1", + "js-sha256": "^0.9.0", + "next": "^9.0.3", "next-redux-wrapper": "^2.0.0", "next-routes": "^1.4.2", + "next-seo": "^1.6.0", "node-pre-gyp": "^0.12.0", "node-sass": "^4.10.0", "react": "^16.6.1", "react-dom": "^16.6.1", - "react-parallax": "^2.0.1", "react-particles-js": "^2.4.2", + "react-player": "^1.9.3", "react-pose": "^4.0.2", "react-redux": "^5.1.0", "react-scroll-parallax": "^1.3.5", - "react-simple-dropdown": "^3.2.3", "react-simple-popover": "^0.2.4", "react-transition-group": "^2.5.0", "redux": "^4.0.1", - "redux-thunk": "^2.3.0" + "redux-thunk": "^2.3.0", + "ua-parser-js": "^0.7.20" }, "devDependencies": { "babel-eslint": "^8.2.5", @@ -41,8 +44,12 @@ "eslint-plugin-react": "^7.9.1" }, "scripts": { - "dev": "node devserver.js NODE_ENV=development", + "dev": "next", "build": "next build", - "start": " node server.js NODE_ENV=production " + "start": "next start", + "now-build": "next build" + }, + "engines": { + "node": "10.x" } } diff --git a/pages/Admin.js b/pages/Admin.js new file mode 100644 index 0000000..76fa314 --- /dev/null +++ b/pages/Admin.js @@ -0,0 +1,1167 @@ +import React, { Component } from 'react'; +import { sha256 } from 'js-sha256'; +import firebase from 'firebase/app'; +import 'firebase/database'; +import { Image } from 'cloudinary-react'; +import { connect } from 'react-redux'; + +import PasswordInput from 'grommet/components/PasswordInput'; +import TextInput from 'grommet/components/TextInput'; +import Button from 'grommet/components/Button'; +import Tabs from 'grommet/components/Tabs'; +import Tab from 'grommet/components/Tab'; +import FormField from 'grommet/components/FormField'; +import RadioButton from 'grommet/components/RadioButton'; +import CloseIcon from 'grommet/components/icons/base/Close'; +import LinkUpIcon from 'grommet/components/icons/base/LinkUp'; +import LinkDownIcon from 'grommet/components/icons/base/LinkDown'; + +import * as types from '../redux/types'; +import CloudinaryInput from '../components/CloudinaryInput'; +import ConfirmModal from '../components/ConfirmModal'; +import { loginUser, logoutUser } from '../redux/actions'; + +class AdminPage extends Component { + static async getInitialProps({ req, store }) { + store.dispatch({ + type: types.CHANGE_PAGE, + payload: '~' + }); + + let db = firebase; + + const project = []; + db.database() + .ref('projects') + .once('value') + .then(datasnapshot => { + datasnapshot.forEach(child => { + project.push(child.key); + }); + }); + + const links = []; + const archive = []; + await db + .database() + .ref('recipients') + .once('value') + .then(datasnapshot => { + datasnapshot.forEach(child => { + if (child.val().archive == true) { + archive.push(child.key); + } else { + links.push(child.key); + } + }); + }); + + store.dispatch({ + type: types.GET_RECIPIENTS, + payload: { links, archive } + }); + + store.dispatch({ + type: types.GET_PROJECTS, + payload: project + }); + } + + state = { + loggedIn: false, + email: 'admin@sageprosthetics.org', + password: '', + logInError: false, + + oldPassword: '', + newPassword: '', + confirmNewPassword: '', + changePasswordState: '', + + uploadImageState: '', + + name: '', + description: '', + student: false, + profilepicture: '', + groupmessage: '', + + facultyList: {}, + facultyError: '', + archiveList: {}, + currentList: {}, + showModal: '', + + hands: {}, + handname: '', + handhref: '', + handsid: '', + handbullets: '', + order: '', + handmessage: '', + + projectname: '', + projectid: '', + projectgroup: [], + projectDescription: '', + projectmessage: '', + projectgallery: [], + + recipients: {}, + recipientname: '', + recipientid: '', + recipientgroup: [], + recipientDescription: '', + recipientmessage: '', + recipientgallery: [], + recipientquote: '' + }; + + database; + + addRecipient = async () => { + console.log('Adding Recipient'); + if (!this.state.recipientname) { + return this.setState({ recipientmessage: 'Error: Please enter a name' }); + } else if (!this.state.recipientid) { + return this.setState({ recipientmessage: 'Error: Please upload a title image' }); + } else if (!this.state.recipientDescription) { + return this.setState({ recipientmessage: 'Error: Please add a description' }); + } + + this.setState({ recipientmessage: 'Updating Database...' }); + try { + const newrecipient = { + src: this.state.recipientid, + text: this.state.recipientDescription, + group: this.state.recipientgroup, + pictures: this.state.recipientgallery, + quote: this.state.recipientquote, + archive: false + }; + + console.log(newrecipient); + + await this.database.ref(`/recipients/${this.state.recipientname}`).set(newrecipient); + + this.setState({ recipientmessage: 'Success' }); + } catch (e) { + this.setState({ recipientmessage: `Error: ${e.message}` }); + } + }; + + async switchRecipient(name) { + const archive = !this.state.recipients[name].archive; + await this.database.ref(`/recipients/${name}/archive`).set(archive); + this.componentDidMount(); + } + + renderRecipients() { + const current = []; + const archive = []; + + Object.keys(this.state.recipients).map((key, index) => { + if (this.state.recipients[key].archive) { + archive.push( + +