├── .babelrc ├── .eslintrc.yml ├── .gitignore ├── .jshintrc ├── CONTRIBUTING.md ├── README.md ├── SUPASUPACHATTY ├── messenger.js ├── ngrok ├── ngrok.zip └── package.json ├── client ├── actions.js ├── app.js ├── components │ ├── ChatBox.js │ ├── CreateProject.js │ ├── Landing.js │ ├── Messages.js │ ├── NotifySystem.js │ ├── Profile.js │ ├── Project.js │ ├── Sidebar.js │ ├── Skills.js │ ├── Swipe.js │ └── UserProfile.js ├── index.js ├── models │ ├── chat.js │ ├── notifications.js │ ├── profile.js │ ├── project.js │ ├── projects.js │ └── users.js ├── public │ ├── animate.css │ ├── images │ │ ├── badge.jpeg │ │ ├── github.jpeg │ │ └── visionary_badge.jpeg │ ├── index.html │ ├── pure.css │ ├── pureResponsive.css │ └── styles.css └── utils.js ├── package.json ├── server ├── apis │ └── github-api.js ├── db.js ├── index.js └── models │ ├── chat.js │ ├── notifications.js │ ├── project.js │ ├── user.js │ └── util.js └── test ├── bootstrap.js ├── run.sh ├── server ├── apis │ ├── projects-api_test.js │ └── users-api_test.js └── models │ ├── project_test.js │ └── user_test.js └── test-helper.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: airbnb 2 | plugins: 3 | - react 4 | ecmaFeatures: 5 | jsx: true 6 | env: 7 | es6: true 8 | browser: true 9 | node: true 10 | jest: true 11 | settings: 12 | import/resolver: webpack 13 | globals: 14 | google: false 15 | twttr: true 16 | Auth0: false 17 | rules: 18 | consistent-return: off 19 | comma-dangle: 20 | - error 21 | - never 22 | no-trailing-spaces: 23 | - error 24 | - skipBlankLines: true 25 | no-confusing-arrow: 26 | - error 27 | - allowParens: true 28 | block-spacing: 29 | - error 30 | - never 31 | arrow-spacing: 32 | - error 33 | - before: true 34 | after: true 35 | object-curly-spacing: off 36 | space-in-parens: 37 | - error 38 | - never 39 | space-before-function-paren: 40 | - error 41 | - never 42 | space-before-blocks: 43 | - error 44 | - always 45 | keyword-spacing: 46 | - error 47 | - before: true 48 | after: true 49 | jsx-quotes: off 50 | quotes: 51 | - error 52 | - single 53 | - avoidEscape: true 54 | allowTemplateLiterals: true 55 | global-require: off 56 | react/jsx-closing-bracket-location: off 57 | import/no-unresolved: off 58 | react/react-in-jsx-scope: off 59 | no-irregular-whitespace: off 60 | no-unused-expressions: 61 | - error 62 | - allowShortCircuit: true 63 | allowTernary: true 64 | no-underscore-dangle: 65 | - error 66 | - allowAfterThis: true 67 | allow: 68 | - _handleNestedListToggle 69 | - _disableDisplay 70 | - _DEPRECATION_NOTICE 71 | - __IS_SMOOTH_SCROLLING 72 | - __scroll__direction 73 | - __CA_DELAYED_MODAL 74 | - _wq 75 | - __dataID__ 76 | - _fbq 77 | - _hsq 78 | no-param-reassign: 79 | - error 80 | - props: false 81 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | dist 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "newcap": false 6 | } 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # gitGeneral Workflow 2 | 3 | 4 | 1. Create a new issue on ZenHub board(ex. issue123) 5 | 6 | -> git checkout -b issue123 7 | 8 | 3. Make changes and stage them for a commit to your feature branch. 9 | 10 | -> git add . 11 | 12 | 4. Commit changes (see commit message guidelines below) 13 | 14 | -> git commit -m 'message' 15 | 16 | 5. Sync up with latest master before pushing to remote feature branch: 17 | 18 | 19 | -> git checkout master 20 | -> git pull 21 | -> git checkout issue123 22 | -> git merge master 23 | 24 | 6. Fix any merge conflicts if necessary. 25 | 26 | 7. Push changes to remote feature branch: 27 | 28 | -> git push origin head 29 | 30 | 8. Generate pull request: 31 | 32 | -> base: master 33 | -> compare: issue123 34 | 35 | 9. Fix any issues highlighted by reviewer if necessary. 36 | 37 | 10. When everything checks out, reviewer merges pull request to master. 38 | 39 | 40 | ## Detailed Workflow 41 | 42 | # Creates your branch and brings you there 43 | 44 | git checkout -b `your-branch-name` 45 | 46 | Make changes and commits on your branch, and make sure that you 47 | only make changes that are relevant to this branch. If you find 48 | yourself making unrelated changes, make a new branch for those 49 | changes. 50 | 51 | #### Commit Message Guidelines 52 | 53 | - Commit messages should be written in the present tense; e.g. "Fix continuous 54 | integration script". 55 | - The first line of your commit message should be a brief summary of what the 56 | commit changes. Aim for about 70 characters max. Remember: This is a summary, 57 | not a detailed description of everything that changed. 58 | - If you want to explain the commit in more depth, following the first line should 59 | be a blank line and then a more detailed description of the commit. This can be 60 | as detailed as you want, so dig into details here and keep the first line short. 61 | 62 | ### Make a pull request 63 | 64 | Make a clear pull request from your fork and branch to the upstream master 65 | branch, detailing exactly what changes you made and what feature this 66 | should add. The clearer your pull request is the faster you can get 67 | your changes incorporated into this repo. 68 | 69 | At least one other person MUST give your changes a code review, and once 70 | they are satisfied they will merge your changes into upstream. Alternatively, 71 | they may have some requested changes. You should make more commits to your 72 | branch to fix these, then follow this process again from merging onwards. 73 | 74 | Once you get back here, make a comment requesting further review and 75 | someone will look at your code again. If they like it, it will get merged, 76 | else, just repeat again. 77 | 78 | Thanks for contributing! 79 | 80 | 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # GitLuv 3 | 4 | Imagine being a lonely developer who is great at ReactNative but no one knows how awesome it is yet, so you can't find any work using it. Imagine having the next Big Idea, but it would require this new language ReactNative that you just heard about but no one knows how to code in. Imagine GitLuv making all these problems go away. By simply logging into GitLuv with your Github credentials, any member of the GitHub community can connect with one another in search of just what they need. 5 | 6 | ### Stack 7 | * React 8 | * Node/Express 9 | * MongoDB/Mongoose 10 | 11 | ### Component Hierarchy 12 | 13 | * Landing 14 | * Swipe 15 | 16 | ### Authors 17 | * Jordan Campbell 18 | * Scott Davison 19 | * Tom LeConey 20 | * Tyler McCarthy 21 | * Kyhan Turner 22 | 23 | ## To start the server 24 | > npm start 25 | 26 | ## To lint the client folder 27 | > npm run lint 28 | -------------------------------------------------------------------------------- /SUPASUPACHATTY/messenger.js: -------------------------------------------------------------------------------- 1 | // Messenger API integration example 2 | // We assume you have: 3 | // * a Wit.ai bot setup (https://wit.ai/docs/quickstart) 4 | // * a Messenger Platform setup (https://developers.facebook.com/docs/messenger-platform/quickstart) 5 | // You need to `npm install` the following dependencies: body-parser, express, request. 6 | // 7 | // 1. npm install body-parser express request 8 | // 2. Download and install ngrok from https://ngrok.com/download 9 | // 3. ./ngrok http 8445 10 | // 4. WIT_TOKEN=your_access_token FB_APP_SECRET=your_app_secret FB_PAGE_TOKEN=your_page_token node examples/messenger.js 11 | // 5. Subscribe your page to the Webhooks using verify_token and `https:///webhook` as callback URL. 12 | // 6. Talk to your bot on Messenger! 13 | 14 | 'use strict'; 15 | const Project = require('../server/models/project') 16 | const bodyParser = require('body-parser'); 17 | const crypto = require('crypto'); 18 | const express = require('express'); 19 | const fetch = require('node-fetch'); 20 | const request = require('request'); 21 | 22 | let Wit = null; 23 | let log = null; 24 | try { 25 | // if running from repo 26 | Wit = require('../').Wit; 27 | log = require('../').log; 28 | } catch (e) { 29 | Wit = require('node-wit').Wit; 30 | log = require('node-wit').log; 31 | } 32 | 33 | // Webserver parameter 34 | const PORT = process.env.PORT || 8445; 35 | 36 | // Wit.ai parameters 37 | const WIT_TOKEN = "47AZU73QCRMK4XOAKEFIW4OTWLLVIA22"; 38 | 39 | // Messenger API parameters 40 | const FB_PAGE_TOKEN = "EAAZAtWUdZCi5YBAPfyZAALW9FItLx1J1pmDWLiZARYgKy5RsbWUk2kTO46ti9ulB1A2Azh0IPpu6ZAw5KazsCP23R87ZB1UVjTBZABXho0yOJmisfOULcQ4ZAH1ZCApoLKQVdNxtyqw6GYZA6Mx6op24J0VPiZCGppnC9FZAOCsRxPKouwZDZD"; 41 | if (!FB_PAGE_TOKEN) { throw new Error('missing FB_PAGE_TOKEN') } 42 | const FB_APP_SECRET = "db9f67e111be93022096f0212cf7f370"; 43 | if (!FB_APP_SECRET) { throw new Error('missing FB_APP_SECRET') } 44 | 45 | let FB_VERIFY_TOKEN = "my_voice_is_my_password_verify_me"; 46 | crypto.randomBytes(8, (err, buff) => { 47 | if (err) throw err; 48 | FB_VERIFY_TOKEN = buff.toString('hex'); 49 | console.log(`/webhook will accept the Verify Token "${FB_VERIFY_TOKEN}"`); 50 | }); 51 | 52 | // ---------------------------------------------------------------------------- 53 | // Messenger API specific code 54 | 55 | // See the Send API reference 56 | // https://developers.facebook.com/docs/messenger-platform/send-api-reference 57 | 58 | const fbMessage = (id, text) => { 59 | const body = JSON.stringify({ 60 | recipient: { id: id }, 61 | message: { text }, 62 | }); 63 | const qs = 'access_token=' + encodeURIComponent(FB_PAGE_TOKEN); 64 | return fetch('https://graph.facebook.com/me/messages?' + qs, { 65 | method: 'POST', 66 | headers: {'Content-Type': 'application/json'}, 67 | body, 68 | }) 69 | .then(rsp => rsp.json()) 70 | .then(json => { 71 | return json; 72 | }); 73 | }; 74 | 75 | // ---------------------------------------------------------------------------- 76 | // Wit.ai bot specific code 77 | 78 | // This will contain all user sessions. 79 | // Each session has an entry: 80 | const sessions = {}; 81 | 82 | const findOrCreateSession = (fbid) => { 83 | let sessionId; 84 | // Let's see if we already have a session for the user fbid 85 | Object.keys(sessions).forEach(k => { 86 | if (sessions[k].fbid === fbid) { 87 | // Yep, got it! 88 | sessionId = k; 89 | } 90 | }); 91 | if (!sessionId) { 92 | // No session found for user fbid, let's create a new one 93 | sessionId = new Date().toISOString(); 94 | sessions[sessionId] = {fbid: fbid, context: {}}; 95 | } 96 | return sessionId; 97 | }; 98 | 99 | const firstEntityValue = (entities, entity) => { 100 | const val = entities && entities[entity] && 101 | Array.isArray(entities[entity]) && 102 | entities[entity].length > 0 && 103 | entities[entity][0].value 104 | ; 105 | if (!val) { 106 | return null; 107 | } 108 | return typeof val === 'object' ? val.value : val; 109 | }; 110 | 111 | // Our bot actions 112 | const actions = { 113 | send({sessionId}, {text}) { 114 | console.log("SESSION IDDDDDD", sessions[sessionId]) 115 | // Our bot has something to say! 116 | // Let's retrieve the Facebook user whose session belongs to 117 | const recipientId = sessions[sessionId].fbid; 118 | if (recipientId) { 119 | // Yay, we found our recipient! 120 | // Let's forward our bot response to her. 121 | // We return a promise to let our bot know when we're done sending 122 | return fbMessage(recipientId, text) 123 | .then(() => null) 124 | .catch((err) => { 125 | console.error( 126 | 'Oops! An error occurred while forwarding the response to', 127 | recipientId, 128 | ':', 129 | err.stack || err 130 | ); 131 | }); 132 | } else { 133 | console.error('Oops! Couldn\'t find user for session:', sessionId); 134 | // Giving the wheel back to our bot 135 | return Promise.resolve() 136 | } 137 | }, 138 | getSkill({context, entities}) { 139 | return new Promise(function(resolve, reject) { 140 | const userSkill = firstEntityValue(entities, 'skill'); 141 | context.skill = userSkill; 142 | const projectList = Project.all() 143 | .then(function(response) { 144 | return response.filter(function (project) { 145 | return project.req_skills.indexOf(userSkill) >= 0 146 | }) 147 | }) 148 | .then(function (goodProj) { 149 | var titles = goodProj.map(function(x) { return x.title }) 150 | context.skill = "react" 151 | context.results = titles 152 | }) 153 | .then(function (res) { 154 | console.log("CONTEXT . RESULTS", context.results) 155 | return resolve(context) 156 | }) 157 | }) 158 | } 159 | }; 160 | 161 | 162 | // Setting up our bot 163 | const wit = new Wit({ 164 | accessToken: WIT_TOKEN, 165 | actions, 166 | logger: new log.Logger(log.INFO) 167 | }); 168 | 169 | // Starting our webserver and putting it all together 170 | const app = express(); 171 | app.use(({method, url}, rsp, next) => { 172 | rsp.on('finish', () => { 173 | console.log(`${rsp.statusCode} ${method} ${url}`); 174 | 175 | }); 176 | next(); 177 | }); 178 | 179 | app.use(bodyParser.json({ verify: verifyRequestSignature })); 180 | 181 | // Webhook setup 182 | app.get('/webhook', (req, res) => { 183 | if (req.query['hub.mode'] === 'subscribe' && 184 | req.query['hub.verify_token'] === FB_VERIFY_TOKEN) { 185 | res.send(req.query['hub.challenge']); 186 | } else { 187 | res.sendStatus(400); 188 | } 189 | }); 190 | 191 | // Message handler 192 | app.post('/webhook', (req, res) => { 193 | // Parse the Messenger payload 194 | // See the Webhook reference 195 | // https://developers.facebook.com/docs/messenger-platform/webhook-reference 196 | const data = req.body; 197 | 198 | if (data.object === 'page') { 199 | data.entry.forEach(entry => { 200 | entry.messaging.forEach(event => { 201 | if (event.message) { 202 | // Received a message 203 | // We retrieve the Facebook user ID of the sender 204 | const sender = event.sender.id; 205 | 206 | // We retrieve the user's current session, or create one if it doesn't exist 207 | // This is needed for our bot to figure out the conversation history 208 | const sessionId = findOrCreateSession(sender); 209 | 210 | // We retrieve the message content 211 | const {text, attachments} = event.message; 212 | 213 | if (attachments) { 214 | // We received an attachment 215 | // Let's reply with an automatic message 216 | fbMessage(sender, 'Sorry I can only process text messages for now.') 217 | .catch(console.error); 218 | } else if (text) { 219 | // We received a text message 220 | 221 | // Let's forward the message to the Wit.ai Bot Engine 222 | // This will run all actions until our bot has nothing left to do 223 | wit.runActions( 224 | sessionId, // the user's current session 225 | text, // the user's message 226 | sessions[sessionId].context // the user's current session state 227 | ).then((context) => { 228 | // Our bot did everything it has to do. 229 | // Now it's waiting for further messages to proceed. 230 | console.log('Waiting for next user messages'); 231 | 232 | // Based on the session state, you might want to reset the session. 233 | // This depends heavily on the business logic of your bot. 234 | // Example: 235 | // if (context['done']) { 236 | // delete sessions[sessionId]; 237 | // } 238 | 239 | // Updating the user's current session state 240 | sessions[sessionId].context = context; 241 | }) 242 | .catch((err) => { 243 | console.error('Oops! Got an error from Wit: ', err.stack || err); 244 | }) 245 | } 246 | } else { 247 | console.log('received event', JSON.stringify(event)); 248 | } 249 | }); 250 | }); 251 | } 252 | res.sendStatus(200); 253 | }); 254 | 255 | /* 256 | * Verify that the callback came from Facebook. Using the App Secret from 257 | * the App Dashboard, we can verify the signature that is sent with each 258 | * callback in the x-hub-signature field, located in the header. 259 | * 260 | * https://developers.facebook.com/docs/graph-api/webhooks#setup 261 | * 262 | */ 263 | function verifyRequestSignature(req, res, buf) { 264 | var signature = req.headers["x-hub-signature"]; 265 | 266 | if (!signature) { 267 | // For testing, let's log an error. In production, you should throw an 268 | // error. 269 | console.error("Couldn't validate the signature."); 270 | } else { 271 | var elements = signature.split('='); 272 | var method = elements[0]; 273 | var signatureHash = elements[1]; 274 | 275 | var expectedHash = crypto.createHmac('sha1', FB_APP_SECRET) 276 | .update(buf) 277 | .digest('hex'); 278 | 279 | if (signatureHash != expectedHash) { 280 | throw new Error("Couldn't validate the request signature."); 281 | } 282 | } 283 | } 284 | 285 | app.listen(PORT); 286 | console.log('Listening on :' + PORT + '...'); -------------------------------------------------------------------------------- /SUPASUPACHATTY/ngrok: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Git-Luv/GitLuv/09f386be2fe7b22159191993debc632d8065d471/SUPASUPACHATTY/ngrok -------------------------------------------------------------------------------- /SUPASUPACHATTY/ngrok.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Git-Luv/GitLuv/09f386be2fe7b22159191993debc632d8065d471/SUPASUPACHATTY/ngrok.zip -------------------------------------------------------------------------------- /SUPASUPACHATTY/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SUPASUPACHATTY", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "body-parser": "^1.15.2", 14 | "express": "^4.14.0", 15 | "json-bigint": "^0.2.0", 16 | "node-fetch": "^1.6.0", 17 | "node-wit": "^4.1.0", 18 | "request": "^2.74.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/actions.js: -------------------------------------------------------------------------------- 1 | export const UPDATE_TEXT = 'UPDATE_TEXT'; 2 | export const CLEAR_TEXT = 'CLEAR_TEXT'; 3 | -------------------------------------------------------------------------------- /client/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Router, Route, browserHistory } from 'react-router'; 3 | import Landing from './components/Landing'; 4 | import Swipe from './components/Swipe'; 5 | import Profile from './components/Profile'; 6 | import Project from './components/Project'; 7 | import Messages from './components/Messages'; 8 | import SkillsList from './components/Skills'; 9 | import UserProfile from './components/UserProfile'; 10 | 11 | // MAIN REACT-ROUTER PAGE // 12 | //////////////////////////// 13 | 14 | export default function App() { 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /client/components/ChatBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Sidebar from './Sidebar'; 3 | import moment from 'moment'; 4 | import * as Chat from '../models/chat'; 5 | import * as model from '../models/profile'; 6 | 7 | var dc = require('delightful-cookies'); 8 | 9 | export default class ChatBox extends React.Component { 10 | 11 | constructor (props) { 12 | super(props); 13 | this.state = { 14 | username : "", 15 | developer: null, 16 | visionary: null, 17 | room : null, 18 | text : "", 19 | messages : [], 20 | open : null, 21 | 22 | } 23 | } 24 | 25 | componentDidMount () { 26 | let self = this 27 | 28 | this.setState({username: this.props.username, 29 | room: this.props.room, 30 | visionary: this.props.visionary, 31 | developer: this.props.developer, 32 | open: this.props.open || null}) 33 | 34 | //chatroom is gotten from database, then applied to state 35 | Chat.getChatroom(this.props.room) 36 | .then(room => { 37 | this.setState({messages: room.messages}) 38 | }) 39 | 40 | //subscribes to socket pertaining to chatroom 41 | socket.emit("subscribe", this.props.room); 42 | 43 | //socket is listening for new messages 44 | socket.on("chat message", function(msg) { 45 | let newMess = [{sentby: msg.sentBy, message: msg.message}] 46 | if(self.state.room === msg.room){ 47 | self.setState({messages: self.state.messages.concat(msg)}) 48 | } 49 | }) 50 | } 51 | 52 | //function runs when message is sent 53 | _handleSubmit (event) { 54 | event.preventDefault(); 55 | 56 | var self = this; 57 | 58 | //socket emitting when a message is sent 59 | socket.emit("send", { 60 | room : this.state.room, 61 | sentBy : this.state.username, 62 | message: this.state.text, 63 | time : moment() 64 | }) 65 | 66 | // clear input after each message 67 | self.setState({ 68 | text: "" 69 | }) 70 | 71 | } 72 | 73 | render () { 74 | 75 | return ( 76 |
77 | 78 | 79 | { 80 | this.state.messages.map(function (msg,index) { 81 | return ( 82 | 83 | ) 84 | }) 85 | } 86 | 87 |
88 |
89 | this.setState({text: event.target.value})} 96 | autoComplete="off" 97 | /> 98 | 99 | 100 |
101 |
102 | ) 103 | } 104 | } 105 | 106 | class Message extends React.Component { 107 | render() { 108 | return ( 109 |
110 |
111 | {this.props.name} 112 | {moment(this.props.time).fromNow()} 113 |
114 |
{this.props.message}
115 |
116 | ) 117 | } 118 | } -------------------------------------------------------------------------------- /client/components/CreateProject.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import * as model from '../models/project'; 4 | import * as Projects from '../models/projects'; 5 | import * as Utils from '../utils' 6 | 7 | var errorTimeoutId; 8 | 9 | export default class CreateProject extends React.Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | stage: 0, 15 | input0: "", 16 | inputArea: "", 17 | inputDesc: "", 18 | project: {}, 19 | error: false, 20 | } 21 | } 22 | 23 | handleChange(event) { 24 | this.setState({ input0: event.target.value }) 25 | } 26 | 27 | handleAreaChange(event) { 28 | this.setState({ inputArea: event.target.value }) 29 | } 30 | 31 | handleDescChange(event) { 32 | this.setState({inputDesc: event.target.value }) 33 | } 34 | 35 | handleCreateRepo(){ 36 | var repoObject = { 37 | name: this.state.input0, 38 | description: this.state.inputDesc, 39 | private: false, 40 | has_issues: true, 41 | has_wiki: true, 42 | has_downloads: true 43 | } 44 | model.createRepo(repoObject) 45 | .then(repo => { 46 | this.setState({ project: { 47 | title: repo.name, 48 | description: repo.description, 49 | username: repo.owner.login, 50 | looking_for: this.state.inputArea, 51 | repo_url: repo.html_url, 52 | description: repo.description, 53 | location: null, 54 | req_skills: [], 55 | users_liked: [], 56 | users_disliked: [], 57 | }, stage: 1 }) 58 | }) 59 | } 60 | 61 | handleNextStage(command) { 62 | switch(this.state.stage){ 63 | case 0: 64 | model.getRepoData(this.state.input0) 65 | .then(repo => { 66 | this.setState({ project: { 67 | title: repo.name, 68 | description: repo.description, 69 | username: repo.owner.login, 70 | looking_for: this.state.inputArea, 71 | repo_url: repo.html_url, 72 | description: repo.description, 73 | location: null, 74 | req_skills: [], 75 | users_liked: [], 76 | users_disliked: [], 77 | }, stage: 1 }) 78 | }) 79 | .catch(err => { 80 | this.setState({ stage: 2 }) 81 | }) 82 | } 83 | } 84 | 85 | handleSkillSelect(skill) { 86 | var temp = this.state.project; 87 | var index = temp.req_skills.indexOf(skill); 88 | if(index > -1){ 89 | temp.req_skills.splice(index, 1) 90 | } else { 91 | temp.req_skills.push(skill); 92 | } 93 | 94 | this.setState({ project: temp }) 95 | } 96 | 97 | submitProject() { 98 | 99 | if(this.state.project.req_skills.length > 0){ 100 | Projects.addProject(this.state.project) 101 | .then(res => { 102 | this.props.project.setState({ isCreatingProject: false, error: false }); 103 | window.alert("Project created!") 104 | }) 105 | } else { 106 | if(errorTimeoutId){ 107 | window.clearTimeout(errorTimeoutId); 108 | } 109 | if(!document.getElementsByClassName('projectWarning')[0]) 110 | document.getElementsByClassName('projectWarning-hidden')[0].className = "projectWarning animated slideInUp" 111 | else { 112 | document.getElementsByClassName('projectWarning')[0].className = "projectWarning animated fadeOut" 113 | window.setTimeout(x => {document.getElementsByClassName('projectWarning')[0].className = "projectWarning animated slideInUp"}, 200) 114 | } 115 | 116 | errorTimeoutId = window.setTimeout(x => { 117 | document.getElementsByClassName('projectWarning')[0].className = "projectWarning animated fadeOut"; 118 | }, 4000) 119 | } 120 | } 121 | 122 | cancelProject() { 123 | this.props.project.setState({ isCreatingProject: false }) 124 | } 125 | 126 | returnStage(stage) { 127 | switch(stage){ 128 | case 0: 129 | return ( 130 |
131 |
132 | 133 |
134 | 135 | 136 |
137 | 138 |
139 |
`You don't have a repo on your GitHub account with that name`
140 |
141 | ) 142 | case 1: 143 | return ( 144 |
145 | 146 |
147 |
148 |

{this.state.project.title}

149 |
Description: {this.state.project.description}
150 |
Please select the skills you require for this project
151 | {Utils.getSkills().map((skill, i) => { 152 | var skillClassName = ''; 153 | if(this.state.project.req_skills.indexOf(skill) > -1) 154 | skillClassName = "skill-selected" 155 | else 156 | skillClassName = "skill-deselected" 157 | return ( 158 | ) 161 | })} 162 |
163 | 164 |
Please choose at least one skill
165 |
166 |
167 | 168 | ) 169 | case 2: 170 | return ( 171 |
172 | 173 |
174 |
175 |
The repo named {this.state.project.title} does not exist, we can create it for you!
176 | 177 |
178 | 179 |
180 |
181 | ) 182 | } 183 | } 184 | 185 | render() { 186 | return ( 187 |
188 |
189 |
190 | { this.returnStage(this.state.stage) } 191 |
192 |
193 |
194 | ) 195 | } 196 | } -------------------------------------------------------------------------------- /client/components/Landing.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import fetch from 'isomorphic-fetch'; 4 | import * as Chat from '../models/chat' 5 | import Sidebar from './Sidebar' 6 | 7 | var dc = require('delightful-cookies'); 8 | 9 | export default class Landing extends React.Component { 10 | constructor(props){ 11 | super(props); 12 | this.state = { 13 | 14 | } 15 | } 16 | 17 | login(e) { 18 | e.preventDefault() 19 | fetch('https://github.com/login/oauth/authorize?client_id=444a46dcbe1340ce4a49&redirect_uri=http://localhost:4000/auth/login&scope=repo', { 20 | method: "GET", 21 | redirect: "manual", 22 | mode: 'no-cors', 23 | }) 24 | .then(res => { 25 | // Redirect to github auth page 26 | document.location.href = res.url; 27 | }) 28 | } 29 | 30 | render() { 31 | return ( 32 |
33 | 42 |
43 |
44 |

GitLuv

45 |
Bring your product to life
46 | 47 |
48 |
49 |
50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /client/components/Messages.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import ChatBox from './ChatBox' 4 | import Sidebar from './Sidebar' 5 | import * as model from '../models/profile'; 6 | import * as Chat from '../models/chat' 7 | 8 | import { Accordion, AccordionItem } from 'react-sanfona'; 9 | 10 | var dc = require('delightful-cookies'); 11 | 12 | export default class Messages extends React.Component { 13 | 14 | constructor (props) { 15 | super(props); 16 | this.state = { 17 | username: "", 18 | chats: [], 19 | chatRoom: null 20 | } 21 | } 22 | 23 | componentDidMount () { 24 | var self = this 25 | 26 | //gets current user data 27 | model.getUserData(dc.get('AuthToken').value) 28 | .then(function(userInfo) { 29 | 30 | self.setState({username: userInfo.login}) 31 | 32 | //maps gets chatrooms, then maps over chatrooms that current user is in. 33 | Chat.getAllChatrooms() 34 | .then(function(chats){ 35 | let chatArr = []; 36 | chats.map(function(chat){ 37 | if( self.state.username === chat.developer || 38 | self.state.username === chat.visionary ){ 39 | chatArr.push(chat) 40 | } 41 | }) 42 | self.setState({chats: chatArr}) 43 | }) 44 | }) 45 | 46 | //start accordion functionality 47 | var acc = document.getElementsByClassName("accordion"); 48 | var i; 49 | 50 | for (i = 0; i < acc.length; i++) { 51 | acc[i].onclick = function(){ 52 | this.classList.toggle("active"); 53 | this.nextElementSibling.classList.toggle("show"); 54 | } 55 | } 56 | 57 | // preexisting to addition of users who like functionality 58 | if(!dc.get('AuthToken')){ 59 | browserHistory.pushState(null, '/') 60 | } 61 | 62 | document.getElementsByClassName('accordion').onclick = function() { 63 | var className = ' ' + accordion.className + ' '; 64 | if ( ~className.indexOf(' active ') ) { 65 | this.className = className.replace(' active ', ' '); 66 | } else { 67 | this.className += ' active'; 68 | } 69 | } 70 | } 71 | 72 | //functionality to open and close the accordion 73 | toggleAccordion(e) { 74 | var acc = document.getElementsByClassName("accordion"); 75 | var i; 76 | 77 | for (i = 0; i < acc.length; i++) { 78 | acc[i].onclick = function(){ 79 | this.classList.toggle("active"); 80 | this.nextElementSibling.classList.toggle("show"); 81 | } 82 | } 83 | } 84 | 85 | render () { 86 | var self = this 87 | return (
88 | 89 | {this.state.chats.map(function(chatBox, i){ 90 | let chatName; 91 | if(self.state.username === chatBox.developer){ 92 | chatName = chatBox.visionary 93 | } else { 94 | chatName = chatBox.developer 95 | } 96 | 97 | return( 98 |
99 | 100 |
101 | 102 |
103 |
104 | ) 105 | })} 106 |
) 107 | } 108 | } 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /client/components/NotifySystem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import * as NotifyModel from '../models/notifications'; 4 | import * as Profile from '../models/profile'; 5 | import * as Utils from '../utils'; 6 | 7 | var dc = require('delightful-cookies'); 8 | 9 | export default class NotifySystem extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | notifications: [], 14 | } 15 | } 16 | 17 | // componentWillMount() { 18 | // Profile.getUserData(dc.get('AuthToken').value) 19 | // .then(res => { 20 | // NotifyModel.get(res.login) 21 | // .then(data => { 22 | // this.setState({ notifications: data }) 23 | // }) 24 | // }) 25 | // } 26 | 27 | handleNotifyClick(item) { 28 | if(!item.isRead){ 29 | NotifyModel.read({ id: item._id }, () => { 30 | this.props.sidebar.updateNotifications.call(this.props.sidebar); 31 | }) 32 | 33 | var temp = []; 34 | for(let i = 0; i < this.props.notifications.length; i++){ 35 | var obj = this.props.notifications[i]; 36 | if(item._id === obj._id){ 37 | obj.isRead = true; 38 | } 39 | temp.push(obj); 40 | } 41 | 42 | this.setState({ notifications: temp }) 43 | } 44 | } 45 | 46 | handleDeleteNotification(item) { 47 | NotifyModel.remove({ id: item._id }) 48 | .then(() => { 49 | this.props.sidebar.updateNotifications.call(this.props.sidebar); 50 | }) 51 | } 52 | 53 | render() { 54 | return( 55 |
56 |
57 | { this.props.notifications.length === 0 ? 58 |
There are currently no notifications
61 | : 62 | this.props.notifications.map((item, i) => { 63 | return ( 64 |
65 | {item.description} 66 |
67 | {Utils.convertTimeToString(item.created)} 68 | X 69 |
70 | ) 71 | }) 72 | } 73 |
74 |
75 | ) 76 | } 77 | } -------------------------------------------------------------------------------- /client/components/Profile.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import fetch from 'isomorphic-fetch'; 4 | import Sidebar from './Sidebar' 5 | 6 | import * as model from '../models/profile'; 7 | import * as Users from '../models/users'; 8 | 9 | var dc = require('delightful-cookies'); 10 | 11 | export default class Profile extends React.Component { 12 | 13 | constructor(props){ 14 | super(props); 15 | this.state = { 16 | userInfo: { 17 | username: null, 18 | location: null, 19 | bio: null, 20 | avatar: null, 21 | }, 22 | userSkills: [], 23 | isSidebar: false, 24 | } 25 | } 26 | 27 | componentWillMount() { 28 | if(dc.get('AuthToken')){ 29 | // Take all browser's cookies and find the one we need 30 | model.getUserData(dc.get('AuthToken').value) 31 | .then(res => { 32 | Users.getUser(res.login) 33 | .then(user => { 34 | this.setState({userInfo: res, userSkills: user.skills}) 35 | }) 36 | }) 37 | } else { 38 | browserHistory.pushState(null, '/'); 39 | } 40 | } 41 | 42 | changeSidebarState(state) { 43 | if(state !== this.state.isSidebar){ 44 | this.setState({ isSidebar: state }) 45 | } 46 | } 47 | 48 | render() { 49 | return ( 50 |
51 | 52 |
53 |
54 |
55 | 56 |
57 |
58 |

{this.state.userInfo.login}

59 |
{this.state.userInfo.location}
60 |
Followers: {this.state.userInfo.followers}
61 |

{this.state.userInfo.bio}

62 |

63 | 64 | 65 | 66 |

67 |
68 | Skills: 69 | {this.state.userSkills.map((skill, i) => { 70 | return() 73 | })} 74 |
75 |
76 |
77 |
78 |
79 | ) 80 | } 81 | } 82 | 83 | // export default class Skill extends React.Component { 84 | // constructor(props){ 85 | // super(props); 86 | // } 87 | 88 | // render() { 89 | // return ( 90 | //
91 | // {this.props.skillName} 92 | //
93 | // ) 94 | // } 95 | // } 96 | -------------------------------------------------------------------------------- /client/components/Project.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import Sidebar from './Sidebar' 4 | import CreateProject from './CreateProject' 5 | import * as Projects from '../models/projects' 6 | import * as modelProfile from '../models/profile'; 7 | import * as modelUser from '../models/users'; 8 | import ChatBox from './ChatBox' 9 | 10 | 11 | import { Accordion, AccordionItem } from 'react-sanfona'; 12 | import { CardStack, Card } from 'react-cardstack'; 13 | 14 | // require('normalize.css'); 15 | var dc = require('delightful-cookies'); 16 | 17 | export default class Project extends React.Component { 18 | constructor(props) { 19 | super(props) 20 | this.state = { 21 | isSidebar: false, 22 | project: null, 23 | isCreatingProject: null, 24 | myProjects: [], 25 | allUsers:[], 26 | user: "" 27 | } 28 | this.toggleAccordion = this.toggleAccordion.bind(this) 29 | this.getUserData = this.getUserData.bind(this) 30 | } 31 | 32 | 33 | //// users who liked your projects functionality //// 34 | ///////////////////////////////////////////////////// 35 | 36 | 37 | componentWillMount() { 38 | // identifies username 39 | let cookie = dc.get("AuthToken") 40 | modelProfile.getUserData(cookie.value) 41 | .then(res => { 42 | this.setState({user: res.login}); 43 | let user = this.state.user; 44 | let creator = this.state.user 45 | Projects.getAllProjects() 46 | .then(projects => { 47 | var temp = []; 48 | projects.forEach((project) => { 49 | if (project.username == creator) { 50 | temp.push(project); 51 | } 52 | }) 53 | this.setState({myProjects: temp}) 54 | }) 55 | }) 56 | 57 | var acc = document.getElementsByClassName("accordion"); 58 | var i; 59 | 60 | for (i = 0; i < acc.length; i++) { 61 | acc[i].onclick = function(){ 62 | this.classList.toggle("active"); 63 | this.nextElementSibling.classList.toggle("show"); 64 | } 65 | } 66 | // preexisting to addition of users who like functionality 67 | if(!dc.get('AuthToken')){ 68 | browserHistory.pushState(null, '/') 69 | } 70 | 71 | document.getElementsByClassName('accordion').onclick = function() { 72 | 73 | var className = ' ' + accordion.className + ' '; 74 | 75 | if ( ~className.indexOf(' active ') ) { 76 | this.className = className.replace(' active ', ' '); 77 | } else { 78 | this.className += ' active'; 79 | } 80 | } 81 | 82 | modelUser.getAllUsers() 83 | .then(res => { 84 | this.setState({allUsers: res}) 85 | }) 86 | } 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | //// sidebar and create project functionality //// 95 | ////////////////////////////////////////////////// 96 | changeSidebarState(state) { 97 | if(state !== this.state.isSidebar){ 98 | this.setState({ isSidebar: state }) 99 | } 100 | } 101 | 102 | createProject() { 103 | this.setState({ isCreatingProject: true }) 104 | } 105 | 106 | toggleAccordion(e) { 107 | var acc = document.getElementsByClassName("accordion"); 108 | var i; 109 | 110 | for (i = 0; i < acc.length; i++) { 111 | acc[i].onclick = function(){ 112 | this.classList.toggle("active"); 113 | this.nextElementSibling.classList.toggle("show"); 114 | } 115 | } 116 | } 117 | 118 | getUserData(user) { 119 | return modelUser.getUser(user) 120 | .then(res => { 121 | return res.avatar_url 122 | }) 123 | } 124 | 125 | getAvatar(user) { 126 | var userObject = this.state.allUsers.filter(function(profile) { 127 | return profile.username === user; 128 | }) 129 | return userObject[0].avatar_url 130 | } 131 | 132 | 133 | 134 | //// render render render render render render //// 135 | ////////////////////////////////////////////////// 136 | render() { 137 | var active 138 | if(!this.state.myProjects.length) { 139 | return ( 140 |
141 | 142 |
143 | { this.state.isCreatingProject ? : null } 144 | 145 |
146 |
147 | ) 148 | } else { 149 | return ( 150 |
151 |
152 | 153 |

My Projects

154 |
155 |
156 | {this.state.myProjects.map((item, i) => { 157 | return ( 158 |
159 | 160 |
161 | {item.users_liked.map(user => { 162 | return ( 163 |
164 | 165 | 166 |

{user}

167 | 168 | 169 |
170 | ) 171 | })} 172 |
173 |
174 | ); 175 | })} 176 |
177 |
178 |
179 |
180 | { this.state.isCreatingProject ? : null } 181 | 182 |
183 |
184 | 185 | ) 186 | } 187 | 188 | } 189 | } 190 | 191 | 192 | -------------------------------------------------------------------------------- /client/components/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import * as Profile from '../models/profile'; 4 | import * as notifyModel from '../models/notifications'; 5 | import NotifySystem from './NotifySystem'; 6 | 7 | var dc = require('delightful-cookies'); 8 | 9 | var updateID; 10 | 11 | export default class SideBar extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | this.toggleMenu = this.toggleMenu.bind(this) 15 | this.toggleHorizontal = this.toggleHorizontal.bind(this) 16 | this.state = { 17 | notifications: [], 18 | username: null, 19 | isNotifySystemOpen: false, 20 | } 21 | } 22 | 23 | componentWillMount() { 24 | if(!this.state.username){ 25 | Profile.getUserData(dc.get('AuthToken').value) 26 | .then(res => { 27 | notifyModel.getUnread(res.login) 28 | .then(data => { 29 | this.setState({ username: res.login, notifications: data }) 30 | }) 31 | }) 32 | } 33 | } 34 | 35 | componentDidMount() { 36 | if(updateID) 37 | window.clearInterval(updateID); 38 | 39 | updateID = window.setInterval(this.updateNotifications.bind(this), 5000); 40 | 41 | } 42 | 43 | toggleNotificationMenu() { 44 | this.setState({ isNotifySystemOpen: !this.state.isNotifySystemOpen }) 45 | if(!this.state.isNotifySystemOpen) 46 | this.updateNotifications(); 47 | } 48 | 49 | updateNotifications() { 50 | notifyModel.get(this.state.username) 51 | .then(data => { 52 | data = data.sort((a, b) => { 53 | a = Date.parse(a.created); 54 | b = Date.parse(b.created); 55 | if(a > b) { 56 | return -1; 57 | } else if(b > a) { 58 | return 1 59 | } else { 60 | return 0; 61 | } 62 | }) 63 | if(data.length > 10) { 64 | for(let i = data.length - 1; i >= 10; i--){ 65 | notifyModel.remove({ id: data[i]._id }) 66 | notifications.pop(); 67 | } 68 | } 69 | this.setState({ notifications: data }) 70 | }) 71 | } 72 | 73 | getNotifyCount() { 74 | var count = 0; 75 | for(let i = 0; i < this.state.notifications.length; i++){ 76 | if(!this.state.notifications[i].isRead){ 77 | count++; 78 | } 79 | } 80 | if(count === 0){ 81 | return null; 82 | } 83 | return count; 84 | } 85 | 86 | logoutUser() { 87 | document.cookie = 'AuthToken=;expires=Thu, 01 Jan 1970 00:00:00 GMT'; 88 | browserHistory.pushState(null, '/') 89 | } 90 | 91 | function (window, document) { 92 | 93 | var menu = document.getElementsByClassName('menuClass'), 94 | WINDOW_CHANGE_EVENT = ('onorientationchange' in window) ? 'orientationchange':'resize'; 95 | 96 | function closeMenu() { 97 | if (menu.classList.contains('open')) { 98 | toggleMenu(); 99 | } 100 | } 101 | 102 | this.refs.toggleClass.addEventListener('click', function (e) { 103 | toggleMenu(); 104 | }); 105 | 106 | window.addEventListener(WINDOW_CHANGE_EVENT, closeMenu); 107 | } 108 | 109 | toggleMenu(e) { 110 | e.preventDefault() 111 | // set timeout so that the panel has a chance to roll up 112 | // before the menu switches states 113 | if (menu.classList.contains('open')) { 114 | setTimeout(this.toggleHorizontal, 500); 115 | 116 | } 117 | else { 118 | 119 | this.toggleHorizontal(); 120 | } 121 | menu.classList.toggle('open'); 122 | this.refs.toggleClass.classList.toggle('x'); 123 | }; 124 | 125 | toggleHorizontal() { 126 | [].forEach.call( 127 | this.refs.menuClass.querySelectorAll('.custom-can-transform'), 128 | function(el){ 129 | el.classList.toggle('pure-menu-horizontal'); 130 | } 131 | ); 132 | }; 133 | 134 | render() { 135 | return( 136 | 162 | ) 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /client/components/Skills.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Router, Route, browserHistory, Link } from 'react-router'; 3 | import fetch from 'isomorphic-fetch'; 4 | import * as modelSkills from '../models/users'; 5 | import * as modelProfile from '../models/profile'; 6 | import * as Utils from '../utils'; 7 | import Sidebar from './Sidebar'; 8 | let dc = require('delightful-cookies') 9 | let Popout = require('react-popout') 10 | 11 | export default class SkillsList extends React.Component { 12 | constructor (props) { 13 | super (props); 14 | // add languages as desired 15 | this.state = { 16 | allSkills: Utils.getSkills(), 17 | userSkills: [], 18 | user: "", 19 | // isPoppedOut: false 20 | }; 21 | // this.popoutClosed = this.popoutClosed.bind(this) 22 | // this.popout = this.popout.bind(this); 23 | this.handleClick = this.handleClick.bind(this); 24 | }; 25 | 26 | popout() { 27 | this.setState({isPoppedOut: true}); 28 | } 29 | 30 | popoutClosed() { 31 | this.setState({isPoppedOut: false}); 32 | } 33 | 34 | componentWillMount() { 35 | let cookie = dc.get("AuthToken") 36 | modelProfile.getUserData(cookie.value) 37 | .then(res => { 38 | this.setState({user: res.login}); 39 | let user = this.state.user; 40 | }) 41 | }; 42 | 43 | handleClick(skill) { 44 | let userSkills = this.state.userSkills; 45 | let index = userSkills.indexOf(skill) 46 | if(index > -1) { 47 | userSkills.splice(index, 1) 48 | } else { 49 | userSkills.push(skill) 50 | } 51 | this.setState({userSkills: userSkills}) 52 | console.log(userSkills) 53 | }; 54 | 55 | sendToDatabase(user, skillz) { 56 | let Popout = this.state.isPoppedOut 57 | let userSkillz = {skills: this.state.userSkills}; 58 | let userName = this.state.user; 59 | if (userSkillz.skills.length < 1 || !userSkillz) { 60 | alert("Please choose at least one skill") 61 | this.setState({isPoppedOut: true}) 62 | console.log("LESS THAN 1") 63 | } else { 64 | modelSkills.updateUser(userName, userSkillz); 65 | browserHistory.pushState(null, '/swipe'); 66 | console.log("ThIS IS DB STUFF", userName, userSkillz) } 67 | }; 68 | 69 | render() { 70 | let skillz = this.state.userSkills; 71 | 72 | // if (this.state.isPoppedOut) { 73 | // return ( 74 | // 75 | //
Popped out content!
76 | //
77 | // ); 78 | // } 79 | 80 | return ( 81 |
82 |
83 | 84 |
85 |

Select your top skills

86 |
87 | {this.state.allSkills.map((skill, i) => {var skillClassName = ''; 88 | if(this.state.userSkills.indexOf(skill) > -1) 89 | skillClassName = "skill-selected" 90 | else 91 | skillClassName = "skill-deselected" 92 | return( 93 | ) 96 | })} 97 |
98 |
99 | 100 |
101 |
102 | ); 103 | }; 104 | }; 105 | -------------------------------------------------------------------------------- /client/components/Swipe.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import Sidebar from './Sidebar'; 4 | import * as Projects from '../models/projects' 5 | import * as model from '../models/profile'; 6 | import * as Users from '../models/users' 7 | import * as Chat from '../models/chat' 8 | import * as Utils from '../utils' 9 | import * as Notify from '../models/notifications'; 10 | 11 | var dc = require('delightful-cookies'); 12 | var hasEvent = false; 13 | 14 | export default class Swipe extends React.Component { 15 | 16 | constructor(props){ 17 | super(props); 18 | this.state = { 19 | isSidebar: false, 20 | projects: null, 21 | username: null, 22 | direction: 'null', 23 | likedProjects: [], 24 | userSkills: [] 25 | } 26 | this.handleLike = this.handleLike.bind(this); 27 | this.handleDislike = this.handleDislike.bind(this); 28 | this.updateArray = this.updateArray.bind(this); 29 | } 30 | 31 | componentDidMount() { 32 | 33 | hasEvent = false; 34 | 35 | if(dc.get('AuthToken')){ 36 | // Take all browser's cookies and find the one we need 37 | model.getUserData(dc.get('AuthToken').value) 38 | .then(res => { 39 | // console.log('res', res) 40 | this.setState({username: res.login}); 41 | 42 | // grab all projects from db 43 | Projects.getAllProjects() 44 | .then(x => { 45 | var allProjects = []; 46 | x.forEach((project) => { 47 | if (project.users_liked.indexOf(res.login) === -1 && project.users_disliked.indexOf(res.login) === -1 && project.username !== res.login) { 48 | allProjects.push(project) 49 | } 50 | }) 51 | // grab user info including user skills 52 | Users.getUser(res.login) 53 | .then(res => { 54 | //add user skills to this.state.userSkills 55 | this.setState({userSkills: res.skills}) 56 | allProjects.forEach(project => { 57 | project.commonSkills = Utils.getCommonSkillCount(res, project); 58 | }) 59 | // Sort based on the amount of commonSkills 60 | allProjects = allProjects.sort((a, b) => { 61 | if(a.commonSkills < b.commonSkills){ 62 | return 1; 63 | } else if(a.commonSkills > b.commonSkills){ 64 | return -1; 65 | } else { 66 | return 0; 67 | } 68 | }) 69 | this.setState({projects: allProjects}) 70 | }) 71 | }) 72 | }) 73 | } else { 74 | browserHistory.pushState(null, '/'); 75 | } 76 | 77 | } 78 | 79 | // update projects array on like 80 | handleLike(event) { 81 | event.preventDefault(); 82 | var self = this 83 | let developer = this.state.username 84 | let visionary = this.state.projects[0].username 85 | 86 | Notify.add({ 87 | description: `A developer has liked your project: ${self.state.projects[0].title}!`, 88 | username: visionary, 89 | }) 90 | 91 | //following function is ran to determine if a chatroom already exists betweem 92 | //visionary and developer 93 | Chat.getChatroom(developer + "" + visionary) 94 | .then(function(x){ 95 | console.log("no") 96 | 97 | Projects.updateProject(self.state.projects[0].title, {users_liked: [self.state.username]}) 98 | self.setState({ direction: 'right' }) 99 | if(!hasEvent) { 100 | document.getElementsByClassName('currentProject')[0].addEventListener('animationend', self.updateArray.bind(self)) 101 | hasEvent = true; 102 | } 103 | 104 | }) 105 | .catch(function(x){ 106 | 107 | Chat.addChatroom({chatRoom: visionary + "" + developer, developer: developer, visionary: visionary, initiated: false}) 108 | .then(function(x) { 109 | Projects.updateProject(self.state.projects[0].title, {users_liked: [self.state.username]}) 110 | self.setState({ direction: 'right' }) 111 | if(!hasEvent) { 112 | document.getElementsByClassName('currentProject')[0].addEventListener('animationend', self.updateArray.bind(self)) 113 | hasEvent = true; 114 | } 115 | }) 116 | 117 | }) 118 | 119 | } 120 | 121 | changeSidebarState(state) { 122 | if(state !== this.state.isSidebar){ 123 | this.setState({ isSidebar: state }) 124 | } 125 | } 126 | 127 | // update users_disliked array on dislike 128 | handleDislike(event) { 129 | event.preventDefault(); 130 | console.log('THISSTATEPROJECTNAME',this.state.projects[0].title) 131 | Projects.updateProject(this.state.projects[0].title, {users_disliked: [this.state.username]}) 132 | this.setState({ direction: 'left'}) 133 | if(!hasEvent) { 134 | document.getElementsByClassName('currentProject')[0].addEventListener('animationend', this.updateArray.bind(this)) 135 | hasEvent = true; 136 | } 137 | } 138 | // highlight matching skills with project and user 139 | handleProjects(skill){ 140 | if(this.state.userSkills.indexOf(skill) >= 0){ 141 | return true; 142 | } 143 | else{ 144 | return false; 145 | } 146 | } 147 | 148 | // display new project on click 149 | updateArray() { 150 | var updatedProjects = this.state.projects.slice(1) 151 | this.setState({ 152 | projects: updatedProjects, 153 | direction: 'null' 154 | }) 155 | } 156 | 157 | render() { 158 | var direction = this.state.direction === 'left' ? 'animated bounceOutLeft' : this.state.direction === 'right' ? 'animated bounceOutRight' : 'null' 159 | 160 | if(this.state.projects === null) { 161 | return ( 162 |
163 | 164 |

Loading...

165 |
166 | ) 167 | } else if (this.state.projects.length === 0) { 168 | return ( 169 |
170 | 171 |

No more projects, check back later!

172 |
173 | ) 174 | } else { 175 | return ( 176 |
177 | 178 |
179 |
180 |

{this.state.projects[0].title}

181 |
182 |

Project Description:

183 |

{this.state.projects[0].description}

184 |

Looking For:

185 |

{this.state.projects[0].looking_for}

186 |

Required Skills:

187 |

{this.state.projects[0].req_skills.map((skill, i) => )}

188 |
189 |
190 |
191 | 192 | 193 |
194 |
195 |
196 | ) 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /client/components/UserProfile.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory, Link } from 'react-router'; 3 | import fetch from 'isomorphic-fetch'; 4 | import Sidebar from './Sidebar' 5 | 6 | import * as model from '../models/profile'; 7 | import * as Users from '../models/users'; 8 | 9 | var dc = require('delightful-cookies'); 10 | 11 | export default class UserProfile extends React.Component { 12 | 13 | constructor(props){ 14 | super(props); 15 | this.state = { 16 | activeUser: '', 17 | userInfo: { 18 | username: null, 19 | location: null, 20 | bio: null, 21 | avatar: null, 22 | endorsements: [] 23 | }, 24 | userSkills: [], 25 | isSidebar: false, 26 | } 27 | this.handleEndorsement = this.handleEndorsement.bind(this); 28 | } 29 | 30 | componentWillMount() { 31 | var url = window.location.href; 32 | var user = url.slice(url.lastIndexOf('/')+1, url.length); 33 | var cookie = dc.get("AuthToken") 34 | 35 | model.getUserData(cookie.value) 36 | .then(res => this.setState({ activeUser: res.login })) 37 | 38 | Users.getUser(user) 39 | .then(user => { 40 | this.setState({userInfo: user, userSkills: user.skills}) 41 | }) 42 | } 43 | 44 | changeSidebarState(state) { 45 | if(state !== this.state.isSidebar){ 46 | this.setState({ isSidebar: state }) 47 | } 48 | } 49 | // endorse user if active user hasn't already and not active user's profile 50 | handleEndorsement(e) { 51 | e.preventDefault(); 52 | var temp = this.state.userInfo; 53 | var activeUser = this.state.activeUser; 54 | var user = this.state.userInfo.username; 55 | var endorsements = this.state.userInfo.endorsements; 56 | if(endorsements.indexOf(activeUser) === -1 && activeUser !== user) { 57 | endorsements.push(this.state.activeUser); 58 | } 59 | temp.endorsements = endorsements; 60 | this.setState({userInfo: temp}) 61 | Users.updateUser(this.state.userInfo.username, {endorsements: endorsements}) 62 | .then(x => console.log('state after update', this.state)) 63 | } 64 | 65 | render() { 66 | console.log('STATE', this.state) 67 | return ( 68 |
69 | 70 |
71 |
72 |
73 | 74 |
75 |
76 |

{this.state.userInfo.username}

77 |
{this.state.userInfo.location}
78 |
Followers: {this.state.userInfo.followers}
79 |
Endorsements: {this.state.userInfo.endorsements.length}
80 |

{this.state.userInfo.bio}

81 |

82 | 83 | 84 | 85 |

86 |
87 | 88 | 89 |
90 |
91 | Skills: 92 | {this.state.userSkills.map((skill, i) => { 93 | return() 96 | })} 97 |
98 |
99 |
100 |
101 |
102 | ) 103 | } 104 | } -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './app'; 4 | 5 | // Render to DOM 6 | ReactDOM.render( 7 | , 8 | document.getElementById('mount') 9 | ); 10 | -------------------------------------------------------------------------------- /client/models/chat.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | require('es6-promise').polyfill(); 3 | var dc = require('delightful-cookies'); 4 | 5 | //function that will return all chatrooms 6 | export function getAllChatrooms(){ 7 | return fetch('/api/chatGET', { 8 | method: 'GET', 9 | headers: { 10 | 'Authorization': dc.get('AuthToken').value, 11 | 'Content-Type': 'application/json' 12 | }}) 13 | .then(data => data.json()) 14 | .catch(err => console.error(err)) 15 | } 16 | 17 | //function that will return a chatroom by specific name 18 | //.catch() returns a rejected promise to trigger .catch() in calling function 19 | export function getChatroom(chatRoom){ 20 | console.log("chatroom?", chatRoom) 21 | return fetch('/api/chat/' + chatRoom, { 22 | method: 'GET', 23 | headers: { 24 | 'Authorization': dc.get('AuthToken').value, 25 | 'Content-Type': 'application/json' 26 | }}) 27 | .then(data => data.json()) 28 | .catch(err => new Promise(function(res, rej){return rej()})) 29 | } 30 | 31 | //function that adds a chatroom, and will update one if it already exists 32 | export function addChatroom(chatRoomObj){ 33 | return fetch('/api/chatPOST', { 34 | method: 'POST', 35 | headers: { 36 | 'Authorization': dc.get('AuthToken').value, 37 | 'Content-Type': 'application/json' 38 | }, 39 | body: JSON.stringify(chatRoomObj) 40 | }) 41 | .then(x => console.log("Added!")) 42 | .catch(err => console.error(err)) 43 | } 44 | 45 | //function that updates a chatroom 46 | export function updateChatroom(chatRoom, updatedAttrs){ 47 | return fetch('/api/chatPATCH', { 48 | method: 'PATCH', 49 | headers: { 50 | 'Authorization': dc.get('AuthToken').value, 51 | 'Content-Type': 'application/json' 52 | }, 53 | body: JSON.stringify([chatRoom, updatedAttrs]) 54 | }) 55 | .then(x => console.log("Patched!")) 56 | .catch(err => console.error(err)) 57 | } -------------------------------------------------------------------------------- /client/models/notifications.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | 3 | // { 4 | // description: String, 5 | // isRead: Boolean, 6 | // username: String, 7 | // } 8 | export function add(obj){ 9 | return fetch('/api/notifications', { 10 | method: "POST", 11 | headers: { 12 | 'Content-Type': 'application/json' 13 | }, 14 | body: JSON.stringify(obj) 15 | }).then(res => { 16 | return res.json(); 17 | }) 18 | } 19 | 20 | // { id: } 21 | export function remove(obj) { 22 | return fetch('/api/notifications', { 23 | method: "DELETE", 24 | headers: { 25 | 'Content-Type': 'application/json' 26 | }, 27 | body: JSON.stringify(obj) 28 | }).then(res => { 29 | return res.json(); 30 | }) 31 | } 32 | 33 | export function getOne(id) { 34 | return fetch('/api/getNotifications/' + id) 35 | .then(res => { 36 | return res.json(); 37 | }) 38 | } 39 | 40 | export function get(username) { 41 | return fetch('/api/notifications/' + username) 42 | .then(res => { 43 | return res.json(); 44 | }) 45 | } 46 | 47 | export function getUnread(username) { 48 | return fetch('/api/unreadNotifications/' + username) 49 | .then(res => { 50 | return res.json(); 51 | }) 52 | } 53 | 54 | export function read(obj) { 55 | return fetch('/api/notifications', { 56 | method: "PATCH", 57 | headers: { 58 | 'Content-Type': 'application/json' 59 | }, 60 | body: JSON.stringify(obj) 61 | }).then(res => { 62 | return res.json(); 63 | }) 64 | } -------------------------------------------------------------------------------- /client/models/profile.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | 3 | export function getUserData(authToken) { 4 | return fetch('https://api.github.com/user', { 5 | method: 'GET', 6 | headers: { 7 | Authorization: "token " + authToken, 8 | Accept: 'application/json' 9 | } 10 | }) 11 | .then(response => { 12 | return response.json(); 13 | }) 14 | } -------------------------------------------------------------------------------- /client/models/project.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | import * as profile from './profile' 3 | 4 | var dc = require('delightful-cookies') 5 | 6 | export function getRepoData(repoName) { 7 | return profile.getUserData(dc.get('AuthToken').value) 8 | .then(response => { 9 | console.log(repoName); 10 | return fetch('https://api.github.com/repos/' + response.login + '/' + repoName, 11 | { 12 | headers: { 13 | Authorization: "token " + dc.get('AuthToken').value, 14 | Accept: 'application/json' 15 | } 16 | }) 17 | .then(response => { 18 | console.log(response) 19 | return response.json(); 20 | }) 21 | .catch(err => { 22 | console.log("ERROR:", err) 23 | }) 24 | }) 25 | } 26 | 27 | export function createRepo(repoObject){ 28 | return profile.getUserData(dc.get('AuthToken').value) 29 | .then(response => { 30 | console.log('response!!!~~~!~~!~!', response) 31 | return fetch('https://api.github.com/user/repos', 32 | { 33 | method:'POST', 34 | headers: { 35 | Authorization: "token " + dc.get('AuthToken').value, 36 | // 'X-OAuth-Scopes': 'repo', 37 | // 'X-Accepted-OAuth-Scopes': 'repo', 38 | Accept: 'application/json' 39 | }, 40 | body: JSON.stringify(repoObject) 41 | }) 42 | .then(response => { 43 | console.log(response) 44 | return response.json(); 45 | }) 46 | .catch(err => { 47 | console.log("ERROR:", err) 48 | }) 49 | }) 50 | } -------------------------------------------------------------------------------- /client/models/projects.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | require('es6-promise').polyfill(); 3 | var dc = require('delightful-cookies'); 4 | 5 | //function that returns all projects 6 | export function getAllProjects(){ 7 | return fetch('/api/projectsGET', { 8 | method: 'GET', 9 | headers: { 10 | 'Authorization': dc.get('AuthToken').value, 11 | 'Content-Type': 'application/json' 12 | }}) 13 | .then(function(data){ 14 | return data.json() 15 | }) 16 | .catch(function(err){ 17 | return console.error(err) 18 | }) 19 | } 20 | 21 | //function that returns a project by title 22 | export function getProject(title){ 23 | return fetch('/api/projects/' + title, { 24 | method: 'GET', 25 | headers: { 26 | 'Authorization': dc.get('AuthToken').value, 27 | 'Content-Type': 'application/json' 28 | }}) 29 | .then(data => data.json()) 30 | .catch(err => console.error(err)) 31 | } 32 | 33 | //function that adds a project and will update one if it exists already 34 | export function addProject(projectObj){ 35 | return fetch('/api/projectsPOST', { 36 | method: 'POST', 37 | headers: { 38 | 'Authorization': dc.get('AuthToken').value, 39 | 'Content-Type': 'application/json' 40 | }, 41 | body: JSON.stringify(projectObj) 42 | }) 43 | .then(x => console.log("Added!")) 44 | .catch(err => console.error(err)) 45 | } 46 | 47 | //function that updates a specific project 48 | export function updateProject(title, updatedAttrs){ 49 | return fetch('/api/projectsPATCH', { 50 | method: 'PATCH', 51 | headers: { 52 | 'Authorization': dc.get('AuthToken').value, 53 | 'Content-Type': 'application/json' 54 | }, 55 | body: JSON.stringify([title, updatedAttrs]) 56 | }) 57 | .then(x => console.log("Patched!", x)) 58 | .catch(err => console.error(err)) 59 | } -------------------------------------------------------------------------------- /client/models/users.js: -------------------------------------------------------------------------------- 1 | import fetch from 'isomorphic-fetch'; 2 | require('es6-promise').polyfill(); 3 | var dc = require('delightful-cookies'); 4 | 5 | //function that gets all users 6 | export function getAllUsers(){ 7 | return fetch('/api/usersGET', { 8 | method: 'GET', 9 | headers: { 10 | 'Authorization': dc.get('AuthToken').value, 11 | 'Content-Type': 'application/json' 12 | }}) 13 | .then(data => data.json()) 14 | .catch(err => console.error(err)) 15 | } 16 | 17 | //function that gets a user by username 18 | export function getUser(username){ 19 | return fetch('/api/users/' + username, { 20 | method: 'GET', 21 | headers: { 22 | 'Authorization': dc.get('AuthToken').value, 23 | 'Content-Type': 'application/json' 24 | }}) 25 | .then(data => data.json()) 26 | .catch(err => console.error(err)) 27 | } 28 | 29 | //function that adds a user and will update one if it already exists 30 | export function addUser(userObj){ 31 | return fetch('/api/usersPOST', { 32 | method: 'POST', 33 | headers: { 34 | 'Authorization': dc.get('AuthToken').value, 35 | 'Content-Type': 'application/json' 36 | }, 37 | body: JSON.stringify(userObj) 38 | }) 39 | .then(x => console.log("Added!")) 40 | .catch(err => console.error(err)) 41 | } 42 | 43 | //function that updates a user by username 44 | export function updateUser(username, updatedAttrs){ 45 | return fetch('/api/usersPATCH', { 46 | method: 'PATCH', 47 | headers: { 48 | 'Authorization': dc.get('AuthToken').value, 49 | 'Content-Type': 'application/json' 50 | }, 51 | body: JSON.stringify([username, updatedAttrs]) 52 | }) 53 | .then(x => console.log("Patched in DB!", x)) 54 | .catch(err => console.error(err)) 55 | } -------------------------------------------------------------------------------- /client/public/images/badge.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Git-Luv/GitLuv/09f386be2fe7b22159191993debc632d8065d471/client/public/images/badge.jpeg -------------------------------------------------------------------------------- /client/public/images/github.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Git-Luv/GitLuv/09f386be2fe7b22159191993debc632d8065d471/client/public/images/github.jpeg -------------------------------------------------------------------------------- /client/public/images/visionary_badge.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Git-Luv/GitLuv/09f386be2fe7b22159191993debc632d8065d471/client/public/images/visionary_badge.jpeg -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | GitLuv 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /client/public/pure.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | /*! 8 | normalize.css v^3.0 | MIT License | git.io/normalize 9 | Copyright (c) Nicolas Gallagher and Jonathan Neal 10 | */ 11 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 12 | 13 | /** 14 | * 1. Set default font family to sans-serif. 15 | * 2. Prevent iOS text size adjust after orientation change, without disabling 16 | * user zoom. 17 | */ 18 | 19 | html { 20 | font-family: sans-serif; /* 1 */ 21 | -ms-text-size-adjust: 100%; /* 2 */ 22 | -webkit-text-size-adjust: 100%; /* 2 */ 23 | } 24 | 25 | /** 26 | * Remove default margin. 27 | */ 28 | 29 | body { 30 | margin: 0; 31 | } 32 | 33 | /* HTML5 display definitions 34 | ========================================================================== */ 35 | 36 | /** 37 | * Correct `block` display not defined for any HTML5 element in IE 8/9. 38 | * Correct `block` display not defined for `details` or `summary` in IE 10/11 39 | * and Firefox. 40 | * Correct `block` display not defined for `main` in IE 11. 41 | */ 42 | 43 | article, 44 | aside, 45 | details, 46 | figcaption, 47 | figure, 48 | footer, 49 | header, 50 | hgroup, 51 | main, 52 | menu, 53 | nav, 54 | section, 55 | summary { 56 | display: block; 57 | } 58 | 59 | /** 60 | * 1. Correct `inline-block` display not defined in IE 8/9. 61 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 62 | */ 63 | 64 | audio, 65 | canvas, 66 | progress, 67 | video { 68 | display: inline-block; /* 1 */ 69 | vertical-align: baseline; /* 2 */ 70 | } 71 | 72 | /** 73 | * Prevent modern browsers from displaying `audio` without controls. 74 | * Remove excess height in iOS 5 devices. 75 | */ 76 | 77 | audio:not([controls]) { 78 | display: none; 79 | height: 0; 80 | } 81 | 82 | /** 83 | * Address `[hidden]` styling not present in IE 8/9/10. 84 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. 85 | */ 86 | 87 | [hidden], 88 | template { 89 | display: none; 90 | } 91 | 92 | /* Links 93 | ========================================================================== */ 94 | 95 | /** 96 | * Remove the gray background color from active links in IE 10. 97 | */ 98 | 99 | a { 100 | background-color: transparent; 101 | } 102 | 103 | /** 104 | * Improve readability when focused and also mouse hovered in all browsers. 105 | */ 106 | 107 | a:active, 108 | a:hover { 109 | outline: 0; 110 | } 111 | 112 | /* Text-level semantics 113 | ========================================================================== */ 114 | 115 | /** 116 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome. 117 | */ 118 | 119 | abbr[title] { 120 | border-bottom: 1px dotted; 121 | } 122 | 123 | /** 124 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. 125 | */ 126 | 127 | b, 128 | strong { 129 | font-weight: bold; 130 | } 131 | 132 | /** 133 | * Address styling not present in Safari and Chrome. 134 | */ 135 | 136 | dfn { 137 | font-style: italic; 138 | } 139 | 140 | /** 141 | * Address variable `h1` font-size and margin within `section` and `article` 142 | * contexts in Firefox 4+, Safari, and Chrome. 143 | */ 144 | 145 | h1 { 146 | font-size: 2em; 147 | margin: 0.67em 0; 148 | } 149 | 150 | /** 151 | * Address styling not present in IE 8/9. 152 | */ 153 | 154 | mark { 155 | background: #ff0; 156 | color: #000; 157 | } 158 | 159 | /** 160 | * Address inconsistent and variable font size in all browsers. 161 | */ 162 | 163 | small { 164 | font-size: 80%; 165 | } 166 | 167 | /** 168 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 169 | */ 170 | 171 | sub, 172 | sup { 173 | font-size: 75%; 174 | line-height: 0; 175 | position: relative; 176 | vertical-align: baseline; 177 | } 178 | 179 | sup { 180 | top: -0.5em; 181 | } 182 | 183 | sub { 184 | bottom: -0.25em; 185 | } 186 | 187 | /* Embedded content 188 | ========================================================================== */ 189 | 190 | /** 191 | * Remove border when inside `a` element in IE 8/9/10. 192 | */ 193 | 194 | img { 195 | border: 0; 196 | } 197 | 198 | /** 199 | * Correct overflow not hidden in IE 9/10/11. 200 | */ 201 | 202 | svg:not(:root) { 203 | overflow: hidden; 204 | } 205 | 206 | /* Grouping content 207 | ========================================================================== */ 208 | 209 | /** 210 | * Address margin not present in IE 8/9 and Safari. 211 | */ 212 | 213 | figure { 214 | margin: 1em 40px; 215 | } 216 | 217 | /** 218 | * Address differences between Firefox and other browsers. 219 | */ 220 | 221 | hr { 222 | -moz-box-sizing: content-box; 223 | box-sizing: content-box; 224 | height: 0; 225 | } 226 | 227 | /** 228 | * Contain overflow in all browsers. 229 | */ 230 | 231 | pre { 232 | overflow: auto; 233 | } 234 | 235 | /** 236 | * Address odd `em`-unit font size rendering in all browsers. 237 | */ 238 | 239 | code, 240 | kbd, 241 | pre, 242 | samp { 243 | font-family: monospace, monospace; 244 | font-size: 1em; 245 | } 246 | 247 | /* Forms 248 | ========================================================================== */ 249 | 250 | /** 251 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 252 | * styling of `select`, unless a `border` property is set. 253 | */ 254 | 255 | /** 256 | * 1. Correct color not being inherited. 257 | * Known issue: affects color of disabled elements. 258 | * 2. Correct font properties not being inherited. 259 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. 260 | */ 261 | 262 | button, 263 | input, 264 | optgroup, 265 | select, 266 | textarea { 267 | color: inherit; /* 1 */ 268 | font: inherit; /* 2 */ 269 | margin: 0; /* 3 */ 270 | } 271 | 272 | /** 273 | * Address `overflow` set to `hidden` in IE 8/9/10/11. 274 | */ 275 | 276 | button { 277 | overflow: visible; 278 | } 279 | 280 | /** 281 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 282 | * All other form control elements do not inherit `text-transform` values. 283 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. 284 | * Correct `select` style inheritance in Firefox. 285 | */ 286 | 287 | button, 288 | select { 289 | text-transform: none; 290 | } 291 | 292 | /** 293 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 294 | * and `video` controls. 295 | * 2. Correct inability to style clickable `input` types in iOS. 296 | * 3. Improve usability and consistency of cursor style between image-type 297 | * `input` and others. 298 | */ 299 | 300 | button, 301 | html input[type="button"], /* 1 */ 302 | input[type="reset"], 303 | input[type="submit"] { 304 | -webkit-appearance: button; /* 2 */ 305 | cursor: pointer; /* 3 */ 306 | } 307 | 308 | /** 309 | * Re-set default cursor for disabled elements. 310 | */ 311 | 312 | button[disabled], 313 | html input[disabled] { 314 | cursor: default; 315 | } 316 | 317 | /** 318 | * Remove inner padding and border in Firefox 4+. 319 | */ 320 | 321 | button::-moz-focus-inner, 322 | input::-moz-focus-inner { 323 | border: 0; 324 | padding: 0; 325 | } 326 | 327 | /** 328 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 329 | * the UA stylesheet. 330 | */ 331 | 332 | input { 333 | line-height: normal; 334 | } 335 | 336 | /** 337 | * It's recommended that you don't attempt to style these elements. 338 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 339 | * 340 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 341 | * 2. Remove excess padding in IE 8/9/10. 342 | */ 343 | 344 | input[type="checkbox"], 345 | input[type="radio"] { 346 | box-sizing: border-box; /* 1 */ 347 | padding: 0; /* 2 */ 348 | } 349 | 350 | /** 351 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 352 | * `font-size` values of the `input`, it causes the cursor style of the 353 | * decrement button to change from `default` to `text`. 354 | */ 355 | 356 | input[type="number"]::-webkit-inner-spin-button, 357 | input[type="number"]::-webkit-outer-spin-button { 358 | height: auto; 359 | } 360 | 361 | /** 362 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome. 363 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome 364 | * (include `-moz` to future-proof). 365 | */ 366 | 367 | input[type="search"] { 368 | -webkit-appearance: textfield; /* 1 */ 369 | -moz-box-sizing: content-box; 370 | -webkit-box-sizing: content-box; /* 2 */ 371 | box-sizing: content-box; 372 | } 373 | 374 | /** 375 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 376 | * Safari (but not Chrome) clips the cancel button when the search input has 377 | * padding (and `textfield` appearance). 378 | */ 379 | 380 | input[type="search"]::-webkit-search-cancel-button, 381 | input[type="search"]::-webkit-search-decoration { 382 | -webkit-appearance: none; 383 | } 384 | 385 | /** 386 | * Define consistent border, margin, and padding. 387 | */ 388 | 389 | fieldset { 390 | border: 1px solid #c0c0c0; 391 | margin: 0 2px; 392 | padding: 0.35em 0.625em 0.75em; 393 | } 394 | 395 | /** 396 | * 1. Correct `color` not being inherited in IE 8/9/10/11. 397 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 398 | */ 399 | 400 | legend { 401 | border: 0; /* 1 */ 402 | padding: 0; /* 2 */ 403 | } 404 | 405 | /** 406 | * Remove default vertical scrollbar in IE 8/9/10/11. 407 | */ 408 | 409 | textarea { 410 | overflow: auto; 411 | } 412 | 413 | /** 414 | * Don't inherit the `font-weight` (applied by a rule above). 415 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 416 | */ 417 | 418 | optgroup { 419 | font-weight: bold; 420 | } 421 | 422 | /* Tables 423 | ========================================================================== */ 424 | 425 | /** 426 | * Remove most spacing between table cells. 427 | */ 428 | 429 | table { 430 | border-collapse: collapse; 431 | border-spacing: 0; 432 | } 433 | 434 | td, 435 | th { 436 | padding: 0; 437 | } 438 | 439 | /*csslint important:false*/ 440 | 441 | /* ========================================================================== 442 | Pure Base Extras 443 | ========================================================================== */ 444 | 445 | /** 446 | * Extra rules that Pure adds on top of Normalize.css 447 | */ 448 | 449 | /** 450 | * Always hide an element when it has the `hidden` HTML attribute. 451 | */ 452 | 453 | .hidden, 454 | [hidden] { 455 | display: none !important; 456 | } 457 | 458 | /** 459 | * Add this class to an image to make it fit within it's fluid parent wrapper while maintaining 460 | * aspect ratio. 461 | */ 462 | .pure-img { 463 | max-width: 100%; 464 | height: auto; 465 | display: block; 466 | } 467 | 468 | /*csslint regex-selectors:false, known-properties:false, duplicate-properties:false*/ 469 | 470 | .pure-g { 471 | letter-spacing: -0.31em; /* Webkit: collapse white-space between units */ 472 | *letter-spacing: normal; /* reset IE < 8 */ 473 | *word-spacing: -0.43em; /* IE < 8: collapse white-space between units */ 474 | text-rendering: optimizespeed; /* Webkit: fixes text-rendering: optimizeLegibility */ 475 | 476 | /* 477 | Sets the font stack to fonts known to work properly with the above letter 478 | and word spacings. See: https://github.com/yahoo/pure/issues/41/ 479 | 480 | The following font stack makes Pure Grids work on all known environments. 481 | 482 | * FreeSans: Ships with many Linux distros, including Ubuntu 483 | 484 | * Arimo: Ships with Chrome OS. Arimo has to be defined before Helvetica and 485 | Arial to get picked up by the browser, even though neither is available 486 | in Chrome OS. 487 | 488 | * Droid Sans: Ships with all versions of Android. 489 | 490 | * Helvetica, Arial, sans-serif: Common font stack on OS X and Windows. 491 | */ 492 | font-family: FreeSans, Arimo, "Droid Sans", Helvetica, Arial, sans-serif; 493 | 494 | /* 495 | Use flexbox when possible to avoid `letter-spacing` side-effects. 496 | 497 | NOTE: Firefox (as of 25) does not currently support flex-wrap, so the 498 | `-moz-` prefix version is omitted. 499 | */ 500 | 501 | display: -webkit-flex; 502 | -webkit-flex-flow: row wrap; 503 | 504 | /* IE10 uses display: flexbox */ 505 | display: -ms-flexbox; 506 | -ms-flex-flow: row wrap; 507 | 508 | /* Prevents distributing space between rows */ 509 | -ms-align-content: flex-start; 510 | -webkit-align-content: flex-start; 511 | align-content: flex-start; 512 | } 513 | 514 | /* Opera as of 12 on Windows needs word-spacing. 515 | The ".opera-only" selector is used to prevent actual prefocus styling 516 | and is not required in markup. 517 | */ 518 | .opera-only :-o-prefocus, 519 | .pure-g { 520 | word-spacing: -0.43em; 521 | } 522 | 523 | .pure-u { 524 | display: inline-block; 525 | *display: inline; /* IE < 8: fake inline-block */ 526 | zoom: 1; 527 | letter-spacing: normal; 528 | word-spacing: normal; 529 | vertical-align: top; 530 | text-rendering: auto; 531 | } 532 | 533 | /* 534 | Resets the font family back to the OS/browser's default sans-serif font, 535 | this the same font stack that Normalize.css sets for the `body`. 536 | */ 537 | .pure-g [class *= "pure-u"] { 538 | font-family: sans-serif; 539 | } 540 | 541 | .pure-u-1, 542 | .pure-u-1-1, 543 | .pure-u-1-2, 544 | .pure-u-1-3, 545 | .pure-u-2-3, 546 | .pure-u-1-4, 547 | .pure-u-3-4, 548 | .pure-u-1-5, 549 | .pure-u-2-5, 550 | .pure-u-3-5, 551 | .pure-u-4-5, 552 | .pure-u-5-5, 553 | .pure-u-1-6, 554 | .pure-u-5-6, 555 | .pure-u-1-8, 556 | .pure-u-3-8, 557 | .pure-u-5-8, 558 | .pure-u-7-8, 559 | .pure-u-1-12, 560 | .pure-u-5-12, 561 | .pure-u-7-12, 562 | .pure-u-11-12, 563 | .pure-u-1-24, 564 | .pure-u-2-24, 565 | .pure-u-3-24, 566 | .pure-u-4-24, 567 | .pure-u-5-24, 568 | .pure-u-6-24, 569 | .pure-u-7-24, 570 | .pure-u-8-24, 571 | .pure-u-9-24, 572 | .pure-u-10-24, 573 | .pure-u-11-24, 574 | .pure-u-12-24, 575 | .pure-u-13-24, 576 | .pure-u-14-24, 577 | .pure-u-15-24, 578 | .pure-u-16-24, 579 | .pure-u-17-24, 580 | .pure-u-18-24, 581 | .pure-u-19-24, 582 | .pure-u-20-24, 583 | .pure-u-21-24, 584 | .pure-u-22-24, 585 | .pure-u-23-24, 586 | .pure-u-24-24 { 587 | display: inline-block; 588 | *display: inline; 589 | zoom: 1; 590 | letter-spacing: normal; 591 | word-spacing: normal; 592 | vertical-align: top; 593 | text-rendering: auto; 594 | } 595 | 596 | .pure-u-1-24 { 597 | width: 4.1667%; 598 | *width: 4.1357%; 599 | } 600 | 601 | .pure-u-1-12, 602 | .pure-u-2-24 { 603 | width: 8.3333%; 604 | *width: 8.3023%; 605 | } 606 | 607 | .pure-u-1-8, 608 | .pure-u-3-24 { 609 | width: 12.5000%; 610 | *width: 12.4690%; 611 | } 612 | 613 | .pure-u-1-6, 614 | .pure-u-4-24 { 615 | width: 16.6667%; 616 | *width: 16.6357%; 617 | } 618 | 619 | .pure-u-1-5 { 620 | width: 20%; 621 | *width: 19.9690%; 622 | } 623 | 624 | .pure-u-5-24 { 625 | width: 20.8333%; 626 | *width: 20.8023%; 627 | } 628 | 629 | .pure-u-1-4, 630 | .pure-u-6-24 { 631 | width: 25%; 632 | *width: 24.9690%; 633 | } 634 | 635 | .pure-u-7-24 { 636 | width: 29.1667%; 637 | *width: 29.1357%; 638 | } 639 | 640 | .pure-u-1-3, 641 | .pure-u-8-24 { 642 | width: 33.3333%; 643 | *width: 33.3023%; 644 | } 645 | 646 | .pure-u-3-8, 647 | .pure-u-9-24 { 648 | width: 37.5000%; 649 | *width: 37.4690%; 650 | } 651 | 652 | .pure-u-2-5 { 653 | width: 40%; 654 | *width: 39.9690%; 655 | } 656 | 657 | .pure-u-5-12, 658 | .pure-u-10-24 { 659 | width: 41.6667%; 660 | *width: 41.6357%; 661 | } 662 | 663 | .pure-u-11-24 { 664 | width: 45.8333%; 665 | *width: 45.8023%; 666 | } 667 | 668 | .pure-u-1-2, 669 | .pure-u-12-24 { 670 | width: 50%; 671 | *width: 49.9690%; 672 | } 673 | 674 | .pure-u-13-24 { 675 | width: 54.1667%; 676 | *width: 54.1357%; 677 | } 678 | 679 | .pure-u-7-12, 680 | .pure-u-14-24 { 681 | width: 58.3333%; 682 | *width: 58.3023%; 683 | } 684 | 685 | .pure-u-3-5 { 686 | width: 60%; 687 | *width: 59.9690%; 688 | } 689 | 690 | .pure-u-5-8, 691 | .pure-u-15-24 { 692 | width: 62.5000%; 693 | *width: 62.4690%; 694 | } 695 | 696 | .pure-u-2-3, 697 | .pure-u-16-24 { 698 | width: 66.6667%; 699 | *width: 66.6357%; 700 | } 701 | 702 | .pure-u-17-24 { 703 | width: 70.8333%; 704 | *width: 70.8023%; 705 | } 706 | 707 | .pure-u-3-4, 708 | .pure-u-18-24 { 709 | width: 75%; 710 | *width: 74.9690%; 711 | } 712 | 713 | .pure-u-19-24 { 714 | width: 79.1667%; 715 | *width: 79.1357%; 716 | } 717 | 718 | .pure-u-4-5 { 719 | width: 80%; 720 | *width: 79.9690%; 721 | } 722 | 723 | .pure-u-5-6, 724 | .pure-u-20-24 { 725 | width: 83.3333%; 726 | *width: 83.3023%; 727 | } 728 | 729 | .pure-u-7-8, 730 | .pure-u-21-24 { 731 | width: 87.5000%; 732 | *width: 87.4690%; 733 | } 734 | 735 | .pure-u-11-12, 736 | .pure-u-22-24 { 737 | width: 91.6667%; 738 | *width: 91.6357%; 739 | } 740 | 741 | .pure-u-23-24 { 742 | width: 95.8333%; 743 | *width: 95.8023%; 744 | } 745 | 746 | .pure-u-1, 747 | .pure-u-1-1, 748 | .pure-u-5-5, 749 | .pure-u-24-24 { 750 | width: 100%; 751 | } 752 | .pure-button { 753 | /* Structure */ 754 | display: inline-block; 755 | zoom: 1; 756 | line-height: normal; 757 | white-space: nowrap; 758 | vertical-align: middle; 759 | text-align: center; 760 | cursor: pointer; 761 | -webkit-user-drag: none; 762 | -webkit-user-select: none; 763 | -moz-user-select: none; 764 | -ms-user-select: none; 765 | user-select: none; 766 | -webkit-box-sizing: border-box; 767 | -moz-box-sizing: border-box; 768 | box-sizing: border-box; 769 | } 770 | 771 | /* Firefox: Get rid of the inner focus border */ 772 | .pure-button::-moz-focus-inner { 773 | padding: 0; 774 | border: 0; 775 | } 776 | 777 | /*csslint outline-none:false*/ 778 | 779 | .pure-button { 780 | font-family: inherit; 781 | font-size: 100%; 782 | padding: 0.5em 1em; 783 | color: #444; /* rgba not supported (IE 8) */ 784 | color: rgba(0, 0, 0, 0.80); /* rgba supported */ 785 | border: 1px solid #999; /*IE 6/7/8*/ 786 | border: none rgba(0, 0, 0, 0); /*IE9 + everything else*/ 787 | background-color: #E6E6E6; 788 | text-decoration: none; 789 | border-radius: 2px; 790 | } 791 | 792 | .pure-button-hover, 793 | .pure-button:hover, 794 | .pure-button:focus { 795 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000',GradientType=0); 796 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(transparent), color-stop(40%, rgba(0,0,0, 0.05)), to(rgba(0,0,0, 0.10))); 797 | background-image: -webkit-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); 798 | background-image: -moz-linear-gradient(top, rgba(0,0,0, 0.05) 0%, rgba(0,0,0, 0.10)); 799 | background-image: -o-linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); 800 | background-image: linear-gradient(transparent, rgba(0,0,0, 0.05) 40%, rgba(0,0,0, 0.10)); 801 | } 802 | .pure-button:focus { 803 | outline: 0; 804 | } 805 | .pure-button-active, 806 | .pure-button:active { 807 | box-shadow: 0 0 0 1px rgba(0,0,0, 0.15) inset, 0 0 6px rgba(0,0,0, 0.20) inset; 808 | border-color: #000\9; 809 | } 810 | 811 | .pure-button[disabled], 812 | .pure-button-disabled, 813 | .pure-button-disabled:hover, 814 | .pure-button-disabled:focus, 815 | .pure-button-disabled:active { 816 | border: none; 817 | background-image: none; 818 | filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); 819 | filter: alpha(opacity=40); 820 | -khtml-opacity: 0.40; 821 | -moz-opacity: 0.40; 822 | opacity: 0.40; 823 | cursor: not-allowed; 824 | box-shadow: none; 825 | } 826 | 827 | .pure-button-hidden { 828 | display: none; 829 | } 830 | 831 | /* Firefox: Get rid of the inner focus border */ 832 | .pure-button::-moz-focus-inner{ 833 | padding: 0; 834 | border: 0; 835 | } 836 | 837 | .pure-button-primary, 838 | .pure-button-selected, 839 | a.pure-button-primary, 840 | a.pure-button-selected { 841 | background-color: rgb(0, 120, 231); 842 | color: #fff; 843 | } 844 | 845 | /*csslint box-model:false*/ 846 | /* 847 | Box-model set to false because we're setting a height on select elements, which 848 | also have border and padding. This is done because some browsers don't render 849 | the padding. We explicitly set the box-model for select elements to border-box, 850 | so we can ignore the csslint warning. 851 | */ 852 | 853 | .pure-form input[type="text"], 854 | .pure-form input[type="password"], 855 | .pure-form input[type="email"], 856 | .pure-form input[type="url"], 857 | .pure-form input[type="date"], 858 | .pure-form input[type="month"], 859 | .pure-form input[type="time"], 860 | .pure-form input[type="datetime"], 861 | .pure-form input[type="datetime-local"], 862 | .pure-form input[type="week"], 863 | .pure-form input[type="number"], 864 | .pure-form input[type="search"], 865 | .pure-form input[type="tel"], 866 | .pure-form input[type="color"], 867 | .pure-form select, 868 | .pure-form textarea { 869 | padding: 0.5em 0.6em; 870 | display: inline-block; 871 | border: 1px solid #ccc; 872 | box-shadow: inset 0 1px 3px #ddd; 873 | border-radius: 4px; 874 | vertical-align: middle; 875 | -webkit-box-sizing: border-box; 876 | -moz-box-sizing: border-box; 877 | box-sizing: border-box; 878 | } 879 | 880 | /* 881 | Need to separate out the :not() selector from the rest of the CSS 2.1 selectors 882 | since IE8 won't execute CSS that contains a CSS3 selector. 883 | */ 884 | .pure-form input:not([type]) { 885 | padding: 0.5em 0.6em; 886 | display: inline-block; 887 | border: 1px solid #ccc; 888 | box-shadow: inset 0 1px 3px #ddd; 889 | border-radius: 4px; 890 | -webkit-box-sizing: border-box; 891 | -moz-box-sizing: border-box; 892 | box-sizing: border-box; 893 | } 894 | 895 | 896 | /* Chrome (as of v.32/34 on OS X) needs additional room for color to display. */ 897 | /* May be able to remove this tweak as color inputs become more standardized across browsers. */ 898 | .pure-form input[type="color"] { 899 | padding: 0.2em 0.5em; 900 | } 901 | 902 | 903 | .pure-form input[type="text"]:focus, 904 | .pure-form input[type="password"]:focus, 905 | .pure-form input[type="email"]:focus, 906 | .pure-form input[type="url"]:focus, 907 | .pure-form input[type="date"]:focus, 908 | .pure-form input[type="month"]:focus, 909 | .pure-form input[type="time"]:focus, 910 | .pure-form input[type="datetime"]:focus, 911 | .pure-form input[type="datetime-local"]:focus, 912 | .pure-form input[type="week"]:focus, 913 | .pure-form input[type="number"]:focus, 914 | .pure-form input[type="search"]:focus, 915 | .pure-form input[type="tel"]:focus, 916 | .pure-form input[type="color"]:focus, 917 | .pure-form select:focus, 918 | .pure-form textarea:focus { 919 | outline: 0; 920 | border-color: #129FEA; 921 | } 922 | 923 | /* 924 | Need to separate out the :not() selector from the rest of the CSS 2.1 selectors 925 | since IE8 won't execute CSS that contains a CSS3 selector. 926 | */ 927 | .pure-form input:not([type]):focus { 928 | outline: 0; 929 | border-color: #129FEA; 930 | } 931 | 932 | .pure-form input[type="file"]:focus, 933 | .pure-form input[type="radio"]:focus, 934 | .pure-form input[type="checkbox"]:focus { 935 | outline: thin solid #129FEA; 936 | outline: 1px auto #129FEA; 937 | } 938 | .pure-form .pure-checkbox, 939 | .pure-form .pure-radio { 940 | margin: 0.5em 0; 941 | display: block; 942 | } 943 | 944 | .pure-form input[type="text"][disabled], 945 | .pure-form input[type="password"][disabled], 946 | .pure-form input[type="email"][disabled], 947 | .pure-form input[type="url"][disabled], 948 | .pure-form input[type="date"][disabled], 949 | .pure-form input[type="month"][disabled], 950 | .pure-form input[type="time"][disabled], 951 | .pure-form input[type="datetime"][disabled], 952 | .pure-form input[type="datetime-local"][disabled], 953 | .pure-form input[type="week"][disabled], 954 | .pure-form input[type="number"][disabled], 955 | .pure-form input[type="search"][disabled], 956 | .pure-form input[type="tel"][disabled], 957 | .pure-form input[type="color"][disabled], 958 | .pure-form select[disabled], 959 | .pure-form textarea[disabled] { 960 | cursor: not-allowed; 961 | background-color: #eaeded; 962 | color: #cad2d3; 963 | } 964 | 965 | /* 966 | Need to separate out the :not() selector from the rest of the CSS 2.1 selectors 967 | since IE8 won't execute CSS that contains a CSS3 selector. 968 | */ 969 | .pure-form input:not([type])[disabled] { 970 | cursor: not-allowed; 971 | background-color: #eaeded; 972 | color: #cad2d3; 973 | } 974 | .pure-form input[readonly], 975 | .pure-form select[readonly], 976 | .pure-form textarea[readonly] { 977 | background-color: #eee; /* menu hover bg color */ 978 | color: #777; /* menu text color */ 979 | border-color: #ccc; 980 | } 981 | 982 | .pure-form input:focus:invalid, 983 | .pure-form textarea:focus:invalid, 984 | .pure-form select:focus:invalid { 985 | color: #b94a48; 986 | border-color: #e9322d; 987 | } 988 | .pure-form input[type="file"]:focus:invalid:focus, 989 | .pure-form input[type="radio"]:focus:invalid:focus, 990 | .pure-form input[type="checkbox"]:focus:invalid:focus { 991 | outline-color: #e9322d; 992 | } 993 | .pure-form select { 994 | /* Normalizes the height; padding is not sufficient. */ 995 | height: 2.25em; 996 | border: 1px solid #ccc; 997 | background-color: white; 998 | } 999 | .pure-form select[multiple] { 1000 | height: auto; 1001 | } 1002 | .pure-form label { 1003 | margin: 0.5em 0 0.2em; 1004 | } 1005 | .pure-form fieldset { 1006 | margin: 0; 1007 | padding: 0.35em 0 0.75em; 1008 | border: 0; 1009 | } 1010 | .pure-form legend { 1011 | display: block; 1012 | width: 100%; 1013 | padding: 0.3em 0; 1014 | margin-bottom: 0.3em; 1015 | color: #333; 1016 | border-bottom: 1px solid #e5e5e5; 1017 | } 1018 | 1019 | .pure-form-stacked input[type="text"], 1020 | .pure-form-stacked input[type="password"], 1021 | .pure-form-stacked input[type="email"], 1022 | .pure-form-stacked input[type="url"], 1023 | .pure-form-stacked input[type="date"], 1024 | .pure-form-stacked input[type="month"], 1025 | .pure-form-stacked input[type="time"], 1026 | .pure-form-stacked input[type="datetime"], 1027 | .pure-form-stacked input[type="datetime-local"], 1028 | .pure-form-stacked input[type="week"], 1029 | .pure-form-stacked input[type="number"], 1030 | .pure-form-stacked input[type="search"], 1031 | .pure-form-stacked input[type="tel"], 1032 | .pure-form-stacked input[type="color"], 1033 | .pure-form-stacked input[type="file"], 1034 | .pure-form-stacked select, 1035 | .pure-form-stacked label, 1036 | .pure-form-stacked textarea { 1037 | display: block; 1038 | margin: 0.25em 0; 1039 | } 1040 | 1041 | /* 1042 | Need to separate out the :not() selector from the rest of the CSS 2.1 selectors 1043 | since IE8 won't execute CSS that contains a CSS3 selector. 1044 | */ 1045 | .pure-form-stacked input:not([type]) { 1046 | display: block; 1047 | margin: 0.25em 0; 1048 | } 1049 | .pure-form-aligned input, 1050 | .pure-form-aligned textarea, 1051 | .pure-form-aligned select, 1052 | /* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */ 1053 | .pure-form-aligned .pure-help-inline, 1054 | .pure-form-message-inline { 1055 | display: inline-block; 1056 | *display: inline; 1057 | *zoom: 1; 1058 | vertical-align: middle; 1059 | } 1060 | .pure-form-aligned textarea { 1061 | vertical-align: top; 1062 | } 1063 | 1064 | /* Aligned Forms */ 1065 | .pure-form-aligned .pure-control-group { 1066 | margin-bottom: 0.5em; 1067 | } 1068 | .pure-form-aligned .pure-control-group label { 1069 | text-align: right; 1070 | display: inline-block; 1071 | vertical-align: middle; 1072 | width: 10em; 1073 | margin: 0 1em 0 0; 1074 | } 1075 | .pure-form-aligned .pure-controls { 1076 | margin: 1.5em 0 0 11em; 1077 | } 1078 | 1079 | /* Rounded Inputs */ 1080 | .pure-form input.pure-input-rounded, 1081 | .pure-form .pure-input-rounded { 1082 | border-radius: 2em; 1083 | padding: 0.5em 1em; 1084 | } 1085 | 1086 | /* Grouped Inputs */ 1087 | .pure-form .pure-group fieldset { 1088 | margin-bottom: 10px; 1089 | } 1090 | .pure-form .pure-group input, 1091 | .pure-form .pure-group textarea { 1092 | display: block; 1093 | padding: 10px; 1094 | margin: 0 0 -1px; 1095 | border-radius: 0; 1096 | position: relative; 1097 | top: -1px; 1098 | } 1099 | .pure-form .pure-group input:focus, 1100 | .pure-form .pure-group textarea:focus { 1101 | z-index: 3; 1102 | } 1103 | .pure-form .pure-group input:first-child, 1104 | .pure-form .pure-group textarea:first-child { 1105 | top: 1px; 1106 | border-radius: 4px 4px 0 0; 1107 | margin: 0; 1108 | } 1109 | .pure-form .pure-group input:first-child:last-child, 1110 | .pure-form .pure-group textarea:first-child:last-child { 1111 | top: 1px; 1112 | border-radius: 4px; 1113 | margin: 0; 1114 | } 1115 | .pure-form .pure-group input:last-child, 1116 | .pure-form .pure-group textarea:last-child { 1117 | top: -2px; 1118 | border-radius: 0 0 4px 4px; 1119 | margin: 0; 1120 | } 1121 | .pure-form .pure-group button { 1122 | margin: 0.35em 0; 1123 | } 1124 | 1125 | .pure-form .pure-input-1 { 1126 | width: 100%; 1127 | } 1128 | .pure-form .pure-input-2-3 { 1129 | width: 66%; 1130 | } 1131 | .pure-form .pure-input-1-2 { 1132 | width: 50%; 1133 | } 1134 | .pure-form .pure-input-1-3 { 1135 | width: 33%; 1136 | } 1137 | .pure-form .pure-input-1-4 { 1138 | width: 25%; 1139 | } 1140 | 1141 | /* Inline help for forms */ 1142 | /* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */ 1143 | .pure-form .pure-help-inline, 1144 | .pure-form-message-inline { 1145 | display: inline-block; 1146 | padding-left: 0.3em; 1147 | color: #666; 1148 | vertical-align: middle; 1149 | font-size: 0.875em; 1150 | } 1151 | 1152 | /* Block help for forms */ 1153 | .pure-form-message { 1154 | display: block; 1155 | color: #666; 1156 | font-size: 0.875em; 1157 | } 1158 | 1159 | @media only screen and (max-width : 480px) { 1160 | .pure-form button[type="submit"] { 1161 | margin: 0.7em 0 0; 1162 | } 1163 | 1164 | .pure-form input:not([type]), 1165 | .pure-form input[type="text"], 1166 | .pure-form input[type="password"], 1167 | .pure-form input[type="email"], 1168 | .pure-form input[type="url"], 1169 | .pure-form input[type="date"], 1170 | .pure-form input[type="month"], 1171 | .pure-form input[type="time"], 1172 | .pure-form input[type="datetime"], 1173 | .pure-form input[type="datetime-local"], 1174 | .pure-form input[type="week"], 1175 | .pure-form input[type="number"], 1176 | .pure-form input[type="search"], 1177 | .pure-form input[type="tel"], 1178 | .pure-form input[type="color"], 1179 | .pure-form label { 1180 | margin-bottom: 0.3em; 1181 | display: block; 1182 | } 1183 | 1184 | .pure-group input:not([type]), 1185 | .pure-group input[type="text"], 1186 | .pure-group input[type="password"], 1187 | .pure-group input[type="email"], 1188 | .pure-group input[type="url"], 1189 | .pure-group input[type="date"], 1190 | .pure-group input[type="month"], 1191 | .pure-group input[type="time"], 1192 | .pure-group input[type="datetime"], 1193 | .pure-group input[type="datetime-local"], 1194 | .pure-group input[type="week"], 1195 | .pure-group input[type="number"], 1196 | .pure-group input[type="search"], 1197 | .pure-group input[type="tel"], 1198 | .pure-group input[type="color"] { 1199 | margin-bottom: 0; 1200 | } 1201 | 1202 | .pure-form-aligned .pure-control-group label { 1203 | margin-bottom: 0.3em; 1204 | text-align: left; 1205 | display: block; 1206 | width: 100%; 1207 | } 1208 | 1209 | .pure-form-aligned .pure-controls { 1210 | margin: 1.5em 0 0 0; 1211 | } 1212 | 1213 | /* NOTE: pure-help-inline is deprecated. Use .pure-form-message-inline instead. */ 1214 | .pure-form .pure-help-inline, 1215 | .pure-form-message-inline, 1216 | .pure-form-message { 1217 | display: block; 1218 | font-size: 0.75em; 1219 | /* Increased bottom padding to make it group with its related input element. */ 1220 | padding: 0.2em 0 0.8em; 1221 | } 1222 | } 1223 | 1224 | /*csslint adjoining-classes: false, box-model:false*/ 1225 | .pure-menu { 1226 | -webkit-box-sizing: border-box; 1227 | -moz-box-sizing: border-box; 1228 | box-sizing: border-box; 1229 | } 1230 | 1231 | .pure-menu-fixed { 1232 | position: fixed; 1233 | left: 0; 1234 | top: 0; 1235 | z-index: 3; 1236 | } 1237 | 1238 | .pure-menu-list, 1239 | .pure-menu-item { 1240 | position: relative; 1241 | } 1242 | 1243 | .pure-menu-list { 1244 | list-style: none; 1245 | margin: 0; 1246 | padding: 0; 1247 | } 1248 | 1249 | .pure-menu-item { 1250 | padding: 0; 1251 | margin: 0; 1252 | height: 100%; 1253 | } 1254 | 1255 | .pure-menu-link, 1256 | .pure-menu-heading { 1257 | display: block; 1258 | text-decoration: none; 1259 | white-space: nowrap; 1260 | } 1261 | 1262 | /* HORIZONTAL MENU */ 1263 | .pure-menu-horizontal { 1264 | width: 100%; 1265 | white-space: nowrap; 1266 | } 1267 | 1268 | .pure-menu-horizontal .pure-menu-list { 1269 | display: inline-block; 1270 | } 1271 | 1272 | /* Initial menus should be inline-block so that they are horizontal */ 1273 | .pure-menu-horizontal .pure-menu-item, 1274 | .pure-menu-horizontal .pure-menu-heading, 1275 | .pure-menu-horizontal .pure-menu-separator { 1276 | display: inline-block; 1277 | *display: inline; 1278 | zoom: 1; 1279 | vertical-align: middle; 1280 | } 1281 | 1282 | /* Submenus should still be display: block; */ 1283 | .pure-menu-item .pure-menu-item { 1284 | display: block; 1285 | } 1286 | 1287 | .pure-menu-children { 1288 | display: none; 1289 | position: absolute; 1290 | left: 100%; 1291 | top: 0; 1292 | margin: 0; 1293 | padding: 0; 1294 | z-index: 3; 1295 | } 1296 | 1297 | .pure-menu-horizontal .pure-menu-children { 1298 | left: 0; 1299 | top: auto; 1300 | width: inherit; 1301 | } 1302 | 1303 | .pure-menu-allow-hover:hover > .pure-menu-children, 1304 | .pure-menu-active > .pure-menu-children { 1305 | display: block; 1306 | position: absolute; 1307 | } 1308 | 1309 | /* Vertical Menus - show the dropdown arrow */ 1310 | .pure-menu-has-children > .pure-menu-link:after { 1311 | padding-left: 0.5em; 1312 | content: "\25B8"; 1313 | font-size: small; 1314 | } 1315 | 1316 | /* Horizontal Menus - show the dropdown arrow */ 1317 | .pure-menu-horizontal .pure-menu-has-children > .pure-menu-link:after { 1318 | content: "\25BE"; 1319 | } 1320 | 1321 | /* scrollable menus */ 1322 | .pure-menu-scrollable { 1323 | overflow-y: scroll; 1324 | overflow-x: hidden; 1325 | } 1326 | 1327 | .pure-menu-scrollable .pure-menu-list { 1328 | display: block; 1329 | } 1330 | 1331 | .pure-menu-horizontal.pure-menu-scrollable .pure-menu-list { 1332 | display: inline-block; 1333 | } 1334 | 1335 | .pure-menu-horizontal.pure-menu-scrollable { 1336 | white-space: nowrap; 1337 | overflow-y: hidden; 1338 | overflow-x: auto; 1339 | -ms-overflow-style: none; 1340 | -webkit-overflow-scrolling: touch; 1341 | /* a little extra padding for this style to allow for scrollbars */ 1342 | padding: .5em 0; 1343 | } 1344 | 1345 | .pure-menu-horizontal.pure-menu-scrollable::-webkit-scrollbar { 1346 | display: none; 1347 | } 1348 | 1349 | /* misc default styling */ 1350 | 1351 | .pure-menu-separator { 1352 | background-color: #ccc; 1353 | height: 1px; 1354 | margin: .3em 0; 1355 | } 1356 | 1357 | .pure-menu-horizontal .pure-menu-separator { 1358 | width: 1px; 1359 | height: 1.3em; 1360 | margin: 0 .3em ; 1361 | } 1362 | 1363 | .pure-menu-heading { 1364 | text-transform: uppercase; 1365 | color: #565d64; 1366 | } 1367 | 1368 | .pure-menu-link { 1369 | color: #777; 1370 | } 1371 | 1372 | .pure-menu-children { 1373 | background-color: #fff; 1374 | } 1375 | 1376 | .pure-menu-link, 1377 | .pure-menu-disabled, 1378 | .pure-menu-heading { 1379 | padding: .5em 1em; 1380 | } 1381 | 1382 | .pure-menu-disabled { 1383 | opacity: .5; 1384 | } 1385 | 1386 | .pure-menu-disabled .pure-menu-link:hover { 1387 | background-color: transparent; 1388 | } 1389 | 1390 | .pure-menu-active > .pure-menu-link, 1391 | .pure-menu-link:hover, 1392 | .pure-menu-link:focus { 1393 | background-color: #eee; 1394 | } 1395 | 1396 | .pure-menu-selected .pure-menu-link, 1397 | .pure-menu-selected .pure-menu-link:visited { 1398 | color: #000; 1399 | } 1400 | 1401 | .pure-table { 1402 | /* Remove spacing between table cells (from Normalize.css) */ 1403 | border-collapse: collapse; 1404 | border-spacing: 0; 1405 | empty-cells: show; 1406 | border: 1px solid #cbcbcb; 1407 | } 1408 | 1409 | .pure-table caption { 1410 | color: #000; 1411 | font: italic 85%/1 arial, sans-serif; 1412 | padding: 1em 0; 1413 | text-align: center; 1414 | } 1415 | 1416 | .pure-table td, 1417 | .pure-table th { 1418 | border-left: 1px solid #cbcbcb;/* inner column border */ 1419 | border-width: 0 0 0 1px; 1420 | font-size: inherit; 1421 | margin: 0; 1422 | overflow: visible; /*to make ths where the title is really long work*/ 1423 | padding: 0.5em 1em; /* cell padding */ 1424 | } 1425 | 1426 | /* Consider removing this next declaration block, as it causes problems when 1427 | there's a rowspan on the first cell. Case added to the tests. issue#432 */ 1428 | .pure-table td:first-child, 1429 | .pure-table th:first-child { 1430 | border-left-width: 0; 1431 | } 1432 | 1433 | .pure-table thead { 1434 | background-color: #e0e0e0; 1435 | color: #000; 1436 | text-align: left; 1437 | vertical-align: bottom; 1438 | } 1439 | 1440 | /* 1441 | striping: 1442 | even - #fff (white) 1443 | odd - #f2f2f2 (light gray) 1444 | */ 1445 | .pure-table td { 1446 | background-color: transparent; 1447 | } 1448 | .pure-table-odd td { 1449 | background-color: #f2f2f2; 1450 | } 1451 | 1452 | /* nth-child selector for modern browsers */ 1453 | .pure-table-striped tr:nth-child(2n-1) td { 1454 | background-color: #f2f2f2; 1455 | } 1456 | 1457 | /* BORDERED TABLES */ 1458 | .pure-table-bordered td { 1459 | border-bottom: 1px solid #cbcbcb; 1460 | } 1461 | .pure-table-bordered tbody > tr:last-child > td { 1462 | border-bottom-width: 0; 1463 | } 1464 | 1465 | 1466 | /* HORIZONTAL BORDERED TABLES */ 1467 | 1468 | .pure-table-horizontal td, 1469 | .pure-table-horizontal th { 1470 | border-width: 0 0 1px 0; 1471 | border-bottom: 1px solid #cbcbcb; 1472 | } 1473 | .pure-table-horizontal tbody > tr:last-child > td { 1474 | border-bottom-width: 0; 1475 | } -------------------------------------------------------------------------------- /client/public/pureResponsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Pure v0.6.0 3 | Copyright 2014 Yahoo! Inc. All rights reserved. 4 | Licensed under the BSD License. 5 | https://github.com/yahoo/pure/blob/master/LICENSE.md 6 | */ 7 | @media screen and (min-width: 35.5em) { 8 | .pure-u-sm-1, 9 | .pure-u-sm-1-1, 10 | .pure-u-sm-1-2, 11 | .pure-u-sm-1-3, 12 | .pure-u-sm-2-3, 13 | .pure-u-sm-1-4, 14 | .pure-u-sm-3-4, 15 | .pure-u-sm-1-5, 16 | .pure-u-sm-2-5, 17 | .pure-u-sm-3-5, 18 | .pure-u-sm-4-5, 19 | .pure-u-sm-5-5, 20 | .pure-u-sm-1-6, 21 | .pure-u-sm-5-6, 22 | .pure-u-sm-1-8, 23 | .pure-u-sm-3-8, 24 | .pure-u-sm-5-8, 25 | .pure-u-sm-7-8, 26 | .pure-u-sm-1-12, 27 | .pure-u-sm-5-12, 28 | .pure-u-sm-7-12, 29 | .pure-u-sm-11-12, 30 | .pure-u-sm-1-24, 31 | .pure-u-sm-2-24, 32 | .pure-u-sm-3-24, 33 | .pure-u-sm-4-24, 34 | .pure-u-sm-5-24, 35 | .pure-u-sm-6-24, 36 | .pure-u-sm-7-24, 37 | .pure-u-sm-8-24, 38 | .pure-u-sm-9-24, 39 | .pure-u-sm-10-24, 40 | .pure-u-sm-11-24, 41 | .pure-u-sm-12-24, 42 | .pure-u-sm-13-24, 43 | .pure-u-sm-14-24, 44 | .pure-u-sm-15-24, 45 | .pure-u-sm-16-24, 46 | .pure-u-sm-17-24, 47 | .pure-u-sm-18-24, 48 | .pure-u-sm-19-24, 49 | .pure-u-sm-20-24, 50 | .pure-u-sm-21-24, 51 | .pure-u-sm-22-24, 52 | .pure-u-sm-23-24, 53 | .pure-u-sm-24-24 { 54 | display: inline-block; 55 | *display: inline; 56 | zoom: 1; 57 | letter-spacing: normal; 58 | word-spacing: normal; 59 | vertical-align: top; 60 | text-rendering: auto; 61 | } 62 | 63 | .pure-u-sm-1-24 { 64 | width: 4.1667%; 65 | *width: 4.1357%; 66 | } 67 | 68 | .pure-u-sm-1-12, 69 | .pure-u-sm-2-24 { 70 | width: 8.3333%; 71 | *width: 8.3023%; 72 | } 73 | 74 | .pure-u-sm-1-8, 75 | .pure-u-sm-3-24 { 76 | width: 12.5000%; 77 | *width: 12.4690%; 78 | } 79 | 80 | .pure-u-sm-1-6, 81 | .pure-u-sm-4-24 { 82 | width: 16.6667%; 83 | *width: 16.6357%; 84 | } 85 | 86 | .pure-u-sm-1-5 { 87 | width: 20%; 88 | *width: 19.9690%; 89 | } 90 | 91 | .pure-u-sm-5-24 { 92 | width: 20.8333%; 93 | *width: 20.8023%; 94 | } 95 | 96 | .pure-u-sm-1-4, 97 | .pure-u-sm-6-24 { 98 | width: 25%; 99 | *width: 24.9690%; 100 | } 101 | 102 | .pure-u-sm-7-24 { 103 | width: 29.1667%; 104 | *width: 29.1357%; 105 | } 106 | 107 | .pure-u-sm-1-3, 108 | .pure-u-sm-8-24 { 109 | width: 33.3333%; 110 | *width: 33.3023%; 111 | } 112 | 113 | .pure-u-sm-3-8, 114 | .pure-u-sm-9-24 { 115 | width: 37.5000%; 116 | *width: 37.4690%; 117 | } 118 | 119 | .pure-u-sm-2-5 { 120 | width: 40%; 121 | *width: 39.9690%; 122 | } 123 | 124 | .pure-u-sm-5-12, 125 | .pure-u-sm-10-24 { 126 | width: 41.6667%; 127 | *width: 41.6357%; 128 | } 129 | 130 | .pure-u-sm-11-24 { 131 | width: 45.8333%; 132 | *width: 45.8023%; 133 | } 134 | 135 | .pure-u-sm-1-2, 136 | .pure-u-sm-12-24 { 137 | width: 50%; 138 | *width: 49.9690%; 139 | } 140 | 141 | .pure-u-sm-13-24 { 142 | width: 54.1667%; 143 | *width: 54.1357%; 144 | } 145 | 146 | .pure-u-sm-7-12, 147 | .pure-u-sm-14-24 { 148 | width: 58.3333%; 149 | *width: 58.3023%; 150 | } 151 | 152 | .pure-u-sm-3-5 { 153 | width: 60%; 154 | *width: 59.9690%; 155 | } 156 | 157 | .pure-u-sm-5-8, 158 | .pure-u-sm-15-24 { 159 | width: 62.5000%; 160 | *width: 62.4690%; 161 | } 162 | 163 | .pure-u-sm-2-3, 164 | .pure-u-sm-16-24 { 165 | width: 66.6667%; 166 | *width: 66.6357%; 167 | } 168 | 169 | .pure-u-sm-17-24 { 170 | width: 70.8333%; 171 | *width: 70.8023%; 172 | } 173 | 174 | .pure-u-sm-3-4, 175 | .pure-u-sm-18-24 { 176 | width: 75%; 177 | *width: 74.9690%; 178 | } 179 | 180 | .pure-u-sm-19-24 { 181 | width: 79.1667%; 182 | *width: 79.1357%; 183 | } 184 | 185 | .pure-u-sm-4-5 { 186 | width: 80%; 187 | *width: 79.9690%; 188 | } 189 | 190 | .pure-u-sm-5-6, 191 | .pure-u-sm-20-24 { 192 | width: 83.3333%; 193 | *width: 83.3023%; 194 | } 195 | 196 | .pure-u-sm-7-8, 197 | .pure-u-sm-21-24 { 198 | width: 87.5000%; 199 | *width: 87.4690%; 200 | } 201 | 202 | .pure-u-sm-11-12, 203 | .pure-u-sm-22-24 { 204 | width: 91.6667%; 205 | *width: 91.6357%; 206 | } 207 | 208 | .pure-u-sm-23-24 { 209 | width: 95.8333%; 210 | *width: 95.8023%; 211 | } 212 | 213 | .pure-u-sm-1, 214 | .pure-u-sm-1-1, 215 | .pure-u-sm-5-5, 216 | .pure-u-sm-24-24 { 217 | width: 100%; 218 | } 219 | } 220 | 221 | @media screen and (min-width: 48em) { 222 | .pure-u-md-1, 223 | .pure-u-md-1-1, 224 | .pure-u-md-1-2, 225 | .pure-u-md-1-3, 226 | .pure-u-md-2-3, 227 | .pure-u-md-1-4, 228 | .pure-u-md-3-4, 229 | .pure-u-md-1-5, 230 | .pure-u-md-2-5, 231 | .pure-u-md-3-5, 232 | .pure-u-md-4-5, 233 | .pure-u-md-5-5, 234 | .pure-u-md-1-6, 235 | .pure-u-md-5-6, 236 | .pure-u-md-1-8, 237 | .pure-u-md-3-8, 238 | .pure-u-md-5-8, 239 | .pure-u-md-7-8, 240 | .pure-u-md-1-12, 241 | .pure-u-md-5-12, 242 | .pure-u-md-7-12, 243 | .pure-u-md-11-12, 244 | .pure-u-md-1-24, 245 | .pure-u-md-2-24, 246 | .pure-u-md-3-24, 247 | .pure-u-md-4-24, 248 | .pure-u-md-5-24, 249 | .pure-u-md-6-24, 250 | .pure-u-md-7-24, 251 | .pure-u-md-8-24, 252 | .pure-u-md-9-24, 253 | .pure-u-md-10-24, 254 | .pure-u-md-11-24, 255 | .pure-u-md-12-24, 256 | .pure-u-md-13-24, 257 | .pure-u-md-14-24, 258 | .pure-u-md-15-24, 259 | .pure-u-md-16-24, 260 | .pure-u-md-17-24, 261 | .pure-u-md-18-24, 262 | .pure-u-md-19-24, 263 | .pure-u-md-20-24, 264 | .pure-u-md-21-24, 265 | .pure-u-md-22-24, 266 | .pure-u-md-23-24, 267 | .pure-u-md-24-24 { 268 | display: inline-block; 269 | *display: inline; 270 | zoom: 1; 271 | letter-spacing: normal; 272 | word-spacing: normal; 273 | vertical-align: top; 274 | text-rendering: auto; 275 | } 276 | 277 | .pure-u-md-1-24 { 278 | width: 4.1667%; 279 | *width: 4.1357%; 280 | } 281 | 282 | .pure-u-md-1-12, 283 | .pure-u-md-2-24 { 284 | width: 8.3333%; 285 | *width: 8.3023%; 286 | } 287 | 288 | .pure-u-md-1-8, 289 | .pure-u-md-3-24 { 290 | width: 12.5000%; 291 | *width: 12.4690%; 292 | } 293 | 294 | .pure-u-md-1-6, 295 | .pure-u-md-4-24 { 296 | width: 16.6667%; 297 | *width: 16.6357%; 298 | } 299 | 300 | .pure-u-md-1-5 { 301 | width: 20%; 302 | *width: 19.9690%; 303 | } 304 | 305 | .pure-u-md-5-24 { 306 | width: 20.8333%; 307 | *width: 20.8023%; 308 | } 309 | 310 | .pure-u-md-1-4, 311 | .pure-u-md-6-24 { 312 | width: 25%; 313 | *width: 24.9690%; 314 | } 315 | 316 | .pure-u-md-7-24 { 317 | width: 29.1667%; 318 | *width: 29.1357%; 319 | } 320 | 321 | .pure-u-md-1-3, 322 | .pure-u-md-8-24 { 323 | width: 33.3333%; 324 | *width: 33.3023%; 325 | } 326 | 327 | .pure-u-md-3-8, 328 | .pure-u-md-9-24 { 329 | width: 37.5000%; 330 | *width: 37.4690%; 331 | } 332 | 333 | .pure-u-md-2-5 { 334 | width: 40%; 335 | *width: 39.9690%; 336 | } 337 | 338 | .pure-u-md-5-12, 339 | .pure-u-md-10-24 { 340 | width: 41.6667%; 341 | *width: 41.6357%; 342 | } 343 | 344 | .pure-u-md-11-24 { 345 | width: 45.8333%; 346 | *width: 45.8023%; 347 | } 348 | 349 | .pure-u-md-1-2, 350 | .pure-u-md-12-24 { 351 | width: 50%; 352 | *width: 49.9690%; 353 | } 354 | 355 | .pure-u-md-13-24 { 356 | width: 54.1667%; 357 | *width: 54.1357%; 358 | } 359 | 360 | .pure-u-md-7-12, 361 | .pure-u-md-14-24 { 362 | width: 58.3333%; 363 | *width: 58.3023%; 364 | } 365 | 366 | .pure-u-md-3-5 { 367 | width: 60%; 368 | *width: 59.9690%; 369 | } 370 | 371 | .pure-u-md-5-8, 372 | .pure-u-md-15-24 { 373 | width: 62.5000%; 374 | *width: 62.4690%; 375 | } 376 | 377 | .pure-u-md-2-3, 378 | .pure-u-md-16-24 { 379 | width: 66.6667%; 380 | *width: 66.6357%; 381 | } 382 | 383 | .pure-u-md-17-24 { 384 | width: 70.8333%; 385 | *width: 70.8023%; 386 | } 387 | 388 | .pure-u-md-3-4, 389 | .pure-u-md-18-24 { 390 | width: 75%; 391 | *width: 74.9690%; 392 | } 393 | 394 | .pure-u-md-19-24 { 395 | width: 79.1667%; 396 | *width: 79.1357%; 397 | } 398 | 399 | .pure-u-md-4-5 { 400 | width: 80%; 401 | *width: 79.9690%; 402 | } 403 | 404 | .pure-u-md-5-6, 405 | .pure-u-md-20-24 { 406 | width: 83.3333%; 407 | *width: 83.3023%; 408 | } 409 | 410 | .pure-u-md-7-8, 411 | .pure-u-md-21-24 { 412 | width: 87.5000%; 413 | *width: 87.4690%; 414 | } 415 | 416 | .pure-u-md-11-12, 417 | .pure-u-md-22-24 { 418 | width: 91.6667%; 419 | *width: 91.6357%; 420 | } 421 | 422 | .pure-u-md-23-24 { 423 | width: 95.8333%; 424 | *width: 95.8023%; 425 | } 426 | 427 | .pure-u-md-1, 428 | .pure-u-md-1-1, 429 | .pure-u-md-5-5, 430 | .pure-u-md-24-24 { 431 | width: 100%; 432 | } 433 | } 434 | 435 | @media screen and (min-width: 64em) { 436 | .pure-u-lg-1, 437 | .pure-u-lg-1-1, 438 | .pure-u-lg-1-2, 439 | .pure-u-lg-1-3, 440 | .pure-u-lg-2-3, 441 | .pure-u-lg-1-4, 442 | .pure-u-lg-3-4, 443 | .pure-u-lg-1-5, 444 | .pure-u-lg-2-5, 445 | .pure-u-lg-3-5, 446 | .pure-u-lg-4-5, 447 | .pure-u-lg-5-5, 448 | .pure-u-lg-1-6, 449 | .pure-u-lg-5-6, 450 | .pure-u-lg-1-8, 451 | .pure-u-lg-3-8, 452 | .pure-u-lg-5-8, 453 | .pure-u-lg-7-8, 454 | .pure-u-lg-1-12, 455 | .pure-u-lg-5-12, 456 | .pure-u-lg-7-12, 457 | .pure-u-lg-11-12, 458 | .pure-u-lg-1-24, 459 | .pure-u-lg-2-24, 460 | .pure-u-lg-3-24, 461 | .pure-u-lg-4-24, 462 | .pure-u-lg-5-24, 463 | .pure-u-lg-6-24, 464 | .pure-u-lg-7-24, 465 | .pure-u-lg-8-24, 466 | .pure-u-lg-9-24, 467 | .pure-u-lg-10-24, 468 | .pure-u-lg-11-24, 469 | .pure-u-lg-12-24, 470 | .pure-u-lg-13-24, 471 | .pure-u-lg-14-24, 472 | .pure-u-lg-15-24, 473 | .pure-u-lg-16-24, 474 | .pure-u-lg-17-24, 475 | .pure-u-lg-18-24, 476 | .pure-u-lg-19-24, 477 | .pure-u-lg-20-24, 478 | .pure-u-lg-21-24, 479 | .pure-u-lg-22-24, 480 | .pure-u-lg-23-24, 481 | .pure-u-lg-24-24 { 482 | display: inline-block; 483 | *display: inline; 484 | zoom: 1; 485 | letter-spacing: normal; 486 | word-spacing: normal; 487 | vertical-align: top; 488 | text-rendering: auto; 489 | } 490 | 491 | .pure-u-lg-1-24 { 492 | width: 4.1667%; 493 | *width: 4.1357%; 494 | } 495 | 496 | .pure-u-lg-1-12, 497 | .pure-u-lg-2-24 { 498 | width: 8.3333%; 499 | *width: 8.3023%; 500 | } 501 | 502 | .pure-u-lg-1-8, 503 | .pure-u-lg-3-24 { 504 | width: 12.5000%; 505 | *width: 12.4690%; 506 | } 507 | 508 | .pure-u-lg-1-6, 509 | .pure-u-lg-4-24 { 510 | width: 16.6667%; 511 | *width: 16.6357%; 512 | } 513 | 514 | .pure-u-lg-1-5 { 515 | width: 20%; 516 | *width: 19.9690%; 517 | } 518 | 519 | .pure-u-lg-5-24 { 520 | width: 20.8333%; 521 | *width: 20.8023%; 522 | } 523 | 524 | .pure-u-lg-1-4, 525 | .pure-u-lg-6-24 { 526 | width: 25%; 527 | *width: 24.9690%; 528 | } 529 | 530 | .pure-u-lg-7-24 { 531 | width: 29.1667%; 532 | *width: 29.1357%; 533 | } 534 | 535 | .pure-u-lg-1-3, 536 | .pure-u-lg-8-24 { 537 | width: 33.3333%; 538 | *width: 33.3023%; 539 | } 540 | 541 | .pure-u-lg-3-8, 542 | .pure-u-lg-9-24 { 543 | width: 37.5000%; 544 | *width: 37.4690%; 545 | } 546 | 547 | .pure-u-lg-2-5 { 548 | width: 40%; 549 | *width: 39.9690%; 550 | } 551 | 552 | .pure-u-lg-5-12, 553 | .pure-u-lg-10-24 { 554 | width: 41.6667%; 555 | *width: 41.6357%; 556 | } 557 | 558 | .pure-u-lg-11-24 { 559 | width: 45.8333%; 560 | *width: 45.8023%; 561 | } 562 | 563 | .pure-u-lg-1-2, 564 | .pure-u-lg-12-24 { 565 | width: 50%; 566 | *width: 49.9690%; 567 | } 568 | 569 | .pure-u-lg-13-24 { 570 | width: 54.1667%; 571 | *width: 54.1357%; 572 | } 573 | 574 | .pure-u-lg-7-12, 575 | .pure-u-lg-14-24 { 576 | width: 58.3333%; 577 | *width: 58.3023%; 578 | } 579 | 580 | .pure-u-lg-3-5 { 581 | width: 60%; 582 | *width: 59.9690%; 583 | } 584 | 585 | .pure-u-lg-5-8, 586 | .pure-u-lg-15-24 { 587 | width: 62.5000%; 588 | *width: 62.4690%; 589 | } 590 | 591 | .pure-u-lg-2-3, 592 | .pure-u-lg-16-24 { 593 | width: 66.6667%; 594 | *width: 66.6357%; 595 | } 596 | 597 | .pure-u-lg-17-24 { 598 | width: 70.8333%; 599 | *width: 70.8023%; 600 | } 601 | 602 | .pure-u-lg-3-4, 603 | .pure-u-lg-18-24 { 604 | width: 75%; 605 | *width: 74.9690%; 606 | } 607 | 608 | .pure-u-lg-19-24 { 609 | width: 79.1667%; 610 | *width: 79.1357%; 611 | } 612 | 613 | .pure-u-lg-4-5 { 614 | width: 80%; 615 | *width: 79.9690%; 616 | } 617 | 618 | .pure-u-lg-5-6, 619 | .pure-u-lg-20-24 { 620 | width: 83.3333%; 621 | *width: 83.3023%; 622 | } 623 | 624 | .pure-u-lg-7-8, 625 | .pure-u-lg-21-24 { 626 | width: 87.5000%; 627 | *width: 87.4690%; 628 | } 629 | 630 | .pure-u-lg-11-12, 631 | .pure-u-lg-22-24 { 632 | width: 91.6667%; 633 | *width: 91.6357%; 634 | } 635 | 636 | .pure-u-lg-23-24 { 637 | width: 95.8333%; 638 | *width: 95.8023%; 639 | } 640 | 641 | .pure-u-lg-1, 642 | .pure-u-lg-1-1, 643 | .pure-u-lg-5-5, 644 | .pure-u-lg-24-24 { 645 | width: 100%; 646 | } 647 | } 648 | 649 | @media screen and (min-width: 80em) { 650 | .pure-u-xl-1, 651 | .pure-u-xl-1-1, 652 | .pure-u-xl-1-2, 653 | .pure-u-xl-1-3, 654 | .pure-u-xl-2-3, 655 | .pure-u-xl-1-4, 656 | .pure-u-xl-3-4, 657 | .pure-u-xl-1-5, 658 | .pure-u-xl-2-5, 659 | .pure-u-xl-3-5, 660 | .pure-u-xl-4-5, 661 | .pure-u-xl-5-5, 662 | .pure-u-xl-1-6, 663 | .pure-u-xl-5-6, 664 | .pure-u-xl-1-8, 665 | .pure-u-xl-3-8, 666 | .pure-u-xl-5-8, 667 | .pure-u-xl-7-8, 668 | .pure-u-xl-1-12, 669 | .pure-u-xl-5-12, 670 | .pure-u-xl-7-12, 671 | .pure-u-xl-11-12, 672 | .pure-u-xl-1-24, 673 | .pure-u-xl-2-24, 674 | .pure-u-xl-3-24, 675 | .pure-u-xl-4-24, 676 | .pure-u-xl-5-24, 677 | .pure-u-xl-6-24, 678 | .pure-u-xl-7-24, 679 | .pure-u-xl-8-24, 680 | .pure-u-xl-9-24, 681 | .pure-u-xl-10-24, 682 | .pure-u-xl-11-24, 683 | .pure-u-xl-12-24, 684 | .pure-u-xl-13-24, 685 | .pure-u-xl-14-24, 686 | .pure-u-xl-15-24, 687 | .pure-u-xl-16-24, 688 | .pure-u-xl-17-24, 689 | .pure-u-xl-18-24, 690 | .pure-u-xl-19-24, 691 | .pure-u-xl-20-24, 692 | .pure-u-xl-21-24, 693 | .pure-u-xl-22-24, 694 | .pure-u-xl-23-24, 695 | .pure-u-xl-24-24 { 696 | display: inline-block; 697 | *display: inline; 698 | zoom: 1; 699 | letter-spacing: normal; 700 | word-spacing: normal; 701 | vertical-align: top; 702 | text-rendering: auto; 703 | } 704 | 705 | .pure-u-xl-1-24 { 706 | width: 4.1667%; 707 | *width: 4.1357%; 708 | } 709 | 710 | .pure-u-xl-1-12, 711 | .pure-u-xl-2-24 { 712 | width: 8.3333%; 713 | *width: 8.3023%; 714 | } 715 | 716 | .pure-u-xl-1-8, 717 | .pure-u-xl-3-24 { 718 | width: 12.5000%; 719 | *width: 12.4690%; 720 | } 721 | 722 | .pure-u-xl-1-6, 723 | .pure-u-xl-4-24 { 724 | width: 16.6667%; 725 | *width: 16.6357%; 726 | } 727 | 728 | .pure-u-xl-1-5 { 729 | width: 20%; 730 | *width: 19.9690%; 731 | } 732 | 733 | .pure-u-xl-5-24 { 734 | width: 20.8333%; 735 | *width: 20.8023%; 736 | } 737 | 738 | .pure-u-xl-1-4, 739 | .pure-u-xl-6-24 { 740 | width: 25%; 741 | *width: 24.9690%; 742 | } 743 | 744 | .pure-u-xl-7-24 { 745 | width: 29.1667%; 746 | *width: 29.1357%; 747 | } 748 | 749 | .pure-u-xl-1-3, 750 | .pure-u-xl-8-24 { 751 | width: 33.3333%; 752 | *width: 33.3023%; 753 | } 754 | 755 | .pure-u-xl-3-8, 756 | .pure-u-xl-9-24 { 757 | width: 37.5000%; 758 | *width: 37.4690%; 759 | } 760 | 761 | .pure-u-xl-2-5 { 762 | width: 40%; 763 | *width: 39.9690%; 764 | } 765 | 766 | .pure-u-xl-5-12, 767 | .pure-u-xl-10-24 { 768 | width: 41.6667%; 769 | *width: 41.6357%; 770 | } 771 | 772 | .pure-u-xl-11-24 { 773 | width: 45.8333%; 774 | *width: 45.8023%; 775 | } 776 | 777 | .pure-u-xl-1-2, 778 | .pure-u-xl-12-24 { 779 | width: 50%; 780 | *width: 49.9690%; 781 | } 782 | 783 | .pure-u-xl-13-24 { 784 | width: 54.1667%; 785 | *width: 54.1357%; 786 | } 787 | 788 | .pure-u-xl-7-12, 789 | .pure-u-xl-14-24 { 790 | width: 58.3333%; 791 | *width: 58.3023%; 792 | } 793 | 794 | .pure-u-xl-3-5 { 795 | width: 60%; 796 | *width: 59.9690%; 797 | } 798 | 799 | .pure-u-xl-5-8, 800 | .pure-u-xl-15-24 { 801 | width: 62.5000%; 802 | *width: 62.4690%; 803 | } 804 | 805 | .pure-u-xl-2-3, 806 | .pure-u-xl-16-24 { 807 | width: 66.6667%; 808 | *width: 66.6357%; 809 | } 810 | 811 | .pure-u-xl-17-24 { 812 | width: 70.8333%; 813 | *width: 70.8023%; 814 | } 815 | 816 | .pure-u-xl-3-4, 817 | .pure-u-xl-18-24 { 818 | width: 75%; 819 | *width: 74.9690%; 820 | } 821 | 822 | .pure-u-xl-19-24 { 823 | width: 79.1667%; 824 | *width: 79.1357%; 825 | } 826 | 827 | .pure-u-xl-4-5 { 828 | width: 80%; 829 | *width: 79.9690%; 830 | } 831 | 832 | .pure-u-xl-5-6, 833 | .pure-u-xl-20-24 { 834 | width: 83.3333%; 835 | *width: 83.3023%; 836 | } 837 | 838 | .pure-u-xl-7-8, 839 | .pure-u-xl-21-24 { 840 | width: 87.5000%; 841 | *width: 87.4690%; 842 | } 843 | 844 | .pure-u-xl-11-12, 845 | .pure-u-xl-22-24 { 846 | width: 91.6667%; 847 | *width: 91.6357%; 848 | } 849 | 850 | .pure-u-xl-23-24 { 851 | width: 95.8333%; 852 | *width: 95.8023%; 853 | } 854 | 855 | .pure-u-xl-1, 856 | .pure-u-xl-1-1, 857 | .pure-u-xl-5-5, 858 | .pure-u-xl-24-24 { 859 | width: 100%; 860 | } 861 | } -------------------------------------------------------------------------------- /client/public/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 115%; 3 | background-color: #5f66f8; 4 | } 5 | 6 | 7 | /* ========== Landing Page ========== */ 8 | 9 | .landingPage { 10 | position: fixed; 11 | width: 100%; 12 | height: 100%; 13 | background-color: #5f66f8; 14 | /*url(http://i.imgur.com/s1ZXO34.gif;);*/ 15 | /*background-image: url(http://res.cloudinary.com/pashadelic/image/upload/cs_srgb,c_limit,h_1550,w_1550/qinekafjcgtqd8l7u1qq.jpg);*/ 16 | background-repeat: no-repeat; 17 | background-size: cover; 18 | center fixed; 19 | /* -webkit-background-size: cover; 20 | -moz-background-size: cover; 21 | -o-background-size: cover; 22 | background-size: cover;*/ 23 | } 24 | 25 | .welcomeh1 { 26 | 27 | color: white; 28 | font-family: 'Rock Salt', cursive; 29 | margin-bottom:10px; 30 | font-size: 40px; 31 | 32 | } 33 | 34 | .gitluvh1 { 35 | color: white; 36 | font-family: 'Pattaya', sans-serif; 37 | margin-bottom:0; 38 | font-size:140px; 39 | } 40 | 41 | 42 | .landingPage button { 43 | width: 150px; 44 | margin-left: 75px; 45 | } 46 | 47 | .landingPage input { 48 | margin-top: 20px; 49 | border-radius: 10px; 50 | margin-left: auto; 51 | margin-right:auto; 52 | display:block; 53 | } 54 | 55 | /* ========== Profile ========== */ 56 | 57 | .description { 58 | text-align: center; 59 | color: white; 60 | margin-bottom:10px; 61 | font-size: 25px; 62 | font-family: 'Raleway', sans-serif; 63 | } 64 | 65 | .profile-right { 66 | font-family: 'Roboto', sans-serif; 67 | } 68 | .projectDescription { 69 | text-align: center; 70 | color: black; 71 | margin-top:20px; 72 | font-family: 'Roboto', sans-serif; 73 | } 74 | 75 | .profile { 76 | width: 50%; 77 | margin: 20px auto; 78 | text-align: center; 79 | /*color: black;*/ 80 | background-color: white; 81 | box-shadow: 0px 0px 8px white; 82 | margin-top: 5%; 83 | padding: 5px; 84 | border-radius: 1%; 85 | } 86 | 87 | .profile img { 88 | height: 150px; 89 | width: 150px; 90 | border-radius: 50%; 91 | } 92 | 93 | /* ========== Skills ========== */ 94 | 95 | .skillPageTitle { 96 | color: white; 97 | font-size: 50px; 98 | text-align: center; 99 | /*margin-bottom: -1px;*/ 100 | /*margin-bottom:10px;*/ 101 | font-family: 'Roboto', sans-serif; 102 | 103 | } 104 | 105 | .button-skillsSelected { 106 | text-align: center; 107 | left:50%; 108 | /*margin-top: 50px;*/ 109 | margin-left: auto; 110 | margin-right:auto; 111 | } 112 | 113 | .skills span { 114 | display: block; 115 | text-align: center; 116 | margin: 0em; 117 | font-size: 25px; 118 | } 119 | 120 | .skill { 121 | display: inline-block; 122 | background-color: #57BC9A; 123 | /*border-radius: 10%;*/ 124 | color: white; 125 | margin: 0.4em 0.6em; 126 | padding: 0.5em 0.7em; 127 | text-align: center; 128 | left:50%; 129 | } 130 | 131 | .skill-selected { 132 | display: inline-block; 133 | background-color: #6cc644; 134 | /*border-radius: 10%;*/ 135 | color: white; 136 | margin: 0.4em 0.6em; 137 | padding: 0.5em 0.7em; 138 | text-align: center; 139 | left: 50%; 140 | } 141 | 142 | .skill-deselected { 143 | display: inline-block; 144 | background-color: #CA3C3C; 145 | /*border-radius: 10%;*/ 146 | color: white; 147 | margin: 0.4em 0.6em; 148 | padding: 0.5em 0.7em; 149 | text-align: center; 150 | left: 50%; 151 | } 152 | 153 | 154 | /* ========== Sidebar ========== */ 155 | 156 | .sidebarOpen { 157 | z-index: 2; 158 | position: fixed; 159 | width: 200px; 160 | height: 100%; 161 | background-image: linear-gradient(#B24592, #F15F79); 162 | top: 0; 163 | left: 0; 164 | transition: left 0.25s; 165 | z-index: 0.75; 166 | } 167 | 168 | .sidebarClose { 169 | z-index: 2; 170 | position: fixed; 171 | width: 200px; 172 | height: 100%; 173 | background-image: linear-gradient(#B24592, #F15F79); 174 | top: 0; 175 | left: -200px; 176 | transition: left 0.25s; 177 | z-index: 0.75; 178 | } 179 | 180 | .sidebarButton { 181 | position: fixed; 182 | left: 1em; 183 | top: 1em; 184 | border-radius: 4px; 185 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); 186 | } 187 | 188 | .sidebar-button { 189 | margin: 5px auto; 190 | display: block; 191 | width: 115px; 192 | background-color: #57BC9A; 193 | color: white; 194 | } 195 | 196 | .sidebar-button-logout { 197 | margin: 5px auto; 198 | display: block; 199 | width: 115px; 200 | background-color: #900C3F; 201 | color: white; 202 | } 203 | 204 | .l-box2 { 205 | padding-right: 2cm; 206 | height: 2.5em; 207 | width: 115px; 208 | } 209 | 210 | .l-box2 img { 211 | zoom: 2; 212 | display: block; 213 | margin: auto; 214 | margin-top: -6px; 215 | max-height: 100%; 216 | 217 | } 218 | 219 | /* ========== Swipe Page ========== */ 220 | 221 | .visionaryBadge { 222 | display: block; 223 | margin-left: auto; 224 | margin-right: auto 225 | } 226 | 227 | .swipe { 228 | margin-top: 5%; 229 | } 230 | 231 | .buttons { 232 | text-align: center; 233 | left:50%; 234 | margin-top: 50px; 235 | margin-left: auto; 236 | margin-right:auto; 237 | } 238 | 239 | .project { 240 | color: dark slate; 241 | font-size: 30px; 242 | text-align: center; 243 | margin-top:0; 244 | margin-bottom:0; 245 | font-family: 'Roboto', sans-serif; 246 | 247 | } 248 | 249 | .button-like, 250 | .button-dislike, 251 | .button-profile { 252 | color: white; 253 | border-radius: 4px; 254 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); 255 | } 256 | 257 | .button-like { 258 | background-color: #57BC9A; /* this is a green */ 259 | } 260 | 261 | .button-dislike { 262 | background-color: #900C3F; /* this is a maroon */ 263 | } 264 | 265 | .button-warning { 266 | background: rgb(223, 117, 20); /* this is an orange */ 267 | } 268 | 269 | .button-profile { 270 | background: rgb(66, 184, 221); /* this is a light blue */ 271 | } 272 | 273 | /* ========== Project ========== */ 274 | 275 | 276 | 277 | .react-sanfona{ 278 | text-align: center; 279 | } 280 | 281 | /* projects that are NOT clicked on color 282 | */.react-sanfona-item{ 283 | color: black 284 | } 285 | 286 | .react-sanfona-item-body{ 287 | color: red 288 | } 289 | 290 | /*project that IS clicked on color*/ 291 | .react-sanfona-item-expanded{ 292 | color: #464ef7 293 | } 294 | 295 | /*users who liked project that is clicked on 296 | */.react-sanfona-item-body-wrapper{ 297 | color: #464ef7 298 | } 299 | 300 | .usersWhoLikedTitle { 301 | color: black; 302 | font-size: 20px; 303 | text-align: center; 304 | margin-bottom:10px; 305 | font-family: 'Roboto', sans-serif; 306 | 307 | } 308 | 309 | .projectsPageTitle{ 310 | color: white; 311 | font-size: 40px; 312 | text-align: center; 313 | margin-top:40px; 314 | margin-bottom:-30px; 315 | font-family: 'Roboto', sans-serif; 316 | 317 | } 318 | 319 | .createProjectModal { 320 | width: 100%; 321 | height: 100%; 322 | top: 0; 323 | left: 0; 324 | position: fixed; 325 | background-color: rgba(0, 0, 0, 0.4); 326 | z-index: 1; 327 | } 328 | 329 | .createProjectSubmit { 330 | margin: 5px auto; 331 | display: block; 332 | width: 115px; 333 | background-color: #6cc644; 334 | color: white; 335 | } 336 | 337 | .projectPage { 338 | width: 50%; 339 | margin: 20px auto; 340 | text-align: center; 341 | color: black; 342 | margin-top: 5%; 343 | padding: 5px; 344 | border-radius: 2%; 345 | } 346 | 347 | .modalContent { 348 | background-color: white; 349 | margin: auto; 350 | margin-top: 30%; 351 | width: 45%; 352 | height: 60%; 353 | color: black; 354 | text-align: center; 355 | box-shadow: 0px 0px 8px white; 356 | /* padding: 5px; 357 | padding-bottom: 40px;*/ 358 | border-radius: 1%; 359 | } 360 | 361 | .modalContentSkills { 362 | background-color: white; 363 | margin: auto; 364 | margin-top: 30%; 365 | width: 70%; 366 | height: 60%; 367 | color: black; 368 | text-align: center; 369 | box-shadow: 0px 0px 8px white; 370 | /* padding: 5px; 371 | padding-bottom: 40px;*/ 372 | border-radius: 1%; 373 | } 374 | 375 | 376 | .stageContent { 377 | color: black; 378 | position: relative; 379 | /*width:70%;*/ 380 | transform: translateY(-50%);*/ 381 | } 382 | 383 | .stage { 384 | padding-top: 40px; 385 | } 386 | 387 | .stage > * { 388 | display: inline-block; 389 | margin: 10px auto; 390 | } 391 | 392 | .projectCancelButton { 393 | display: inline-block; 394 | position: absolute; 395 | top: 5%; 396 | left: 28%; 397 | margin: 0px; 398 | padding: 5px; 399 | /* margin-top: -9%; */ 400 | float: left; 401 | } 402 | 403 | .projectCancelButtonSkills { 404 | display: inline-block; 405 | position: relative; 406 | top: 25px; 407 | left: 10px; 408 | margin: 0px; 409 | padding: 5px; 410 | margin-top: -18px; 411 | float: left; 412 | } 413 | 414 | .projectWarning { 415 | display: block; 416 | color: red; 417 | } 418 | 419 | .projectWarning-hidden { 420 | display: none; 421 | } 422 | 423 | .skillsSelector div { 424 | display: inline-block; 425 | background-color: #6cc644; 426 | border-radius: 10%; 427 | color: white; 428 | margin: 0.4em 0.6em; 429 | padding: 0.5em 0.7em; 430 | text-align: center; 431 | max-width: ; 432 | } 433 | 434 | .loading { 435 | color: white; 436 | opacity: 0.9; 437 | font-size: 30px; 438 | text-align: center; 439 | margin-bottom:0; 440 | margin-top:300px; 441 | font-family: 'Roboto', sans-serif; 442 | 443 | } 444 | .landing-text { 445 | color: #2f38f6; 446 | font-size: 30px; 447 | text-align: center; 448 | margin-bottom:0; 449 | margin-top:180px; 450 | } 451 | 452 | .create-project { 453 | position: relative; 454 | text-align:center; 455 | padding-left: 25%; 456 | width: 100%; 457 | } 458 | 459 | .project-next { 460 | float:left; 461 | } 462 | 463 | .currentProject { 464 | color: black; 465 | text-align: center; 466 | width: 40%; 467 | margin: 10px auto; 468 | text-align: center; 469 | background-color: white; 470 | box-shadow: 0px 0px 8px white; 471 | margin-top: 5%; 472 | padding: 5px; 473 | padding-bottom: 40px; 474 | border-radius: 1%; 475 | } 476 | 477 | .skillSelector { 478 | color: black; 479 | text-align: center; 480 | width: 70%; 481 | margin: 10px auto; 482 | text-align: center; 483 | /*background-color: white;*/ 484 | /*box-shadow: 0px 0px 20px black;*/ 485 | /* margin-top: 5%; 486 | margin-bottom: 5%;*/ 487 | padding: 5px; 488 | /*padding-bottom: 40px;*/ 489 | border-radius: 2%; 490 | } 491 | 492 | .projectsLiked { 493 | color: black; 494 | text-align: center; 495 | width: 40%; 496 | margin: 10px auto; 497 | text-align: center; 498 | background-color: white; 499 | box-shadow: 0px 0px 8px white; 500 | margin-top: 5%; 501 | padding: 15px; 502 | border-radius: 1%; 503 | font-family: 'Roboto', sans-serif; 504 | } 505 | .horizontal-rule { 506 | display: block; 507 | height: 1px; 508 | border: 0; 509 | border-top: 1px solid #ccc; 510 | margin: 1em 0; 511 | padding: 0; 512 | } 513 | 514 | .likedUsers { 515 | text-align: left; 516 | } 517 | 518 | .toGithub img{ 519 | width: 20%; 520 | height: 20%; 521 | border-radius: 0; 522 | /*position: relative;*/ 523 | } 524 | 525 | /*MENU*/ 526 | .custom-wrapper { 527 | background-color:#464ef7; 528 | margin-bottom: 1em; 529 | -webkit-font-smoothing: antialiased; 530 | height: 2.5em; 531 | overflow: hidden; 532 | -webkit-transition: height 0.5s; 533 | -moz-transition: height 0.5s; 534 | -ms-transition: height 0.5s; 535 | transition: height 0.5s; 536 | } 537 | 538 | .showProjects { 539 | background-color: #464ef7; 540 | color: white; 541 | font-family: 'Roboto', sans-serif; 542 | font-size: 18px; 543 | margin-top: 25px; 544 | 545 | } 546 | 547 | .custom-wrapper.open { 548 | height: 26.5em; 549 | } 550 | 551 | .custom-menu-3 { 552 | text-align: right; 553 | } 554 | 555 | .custom-toggle { 556 | width: 34px; 557 | height: 34px; 558 | display: block; 559 | position: absolute; 560 | top: 0; 561 | right: 0; 562 | display: none; 563 | } 564 | 565 | .custom-toggle .bar { 566 | background-color: white; 567 | display: block; 568 | width: 20px; 569 | height: 2px; 570 | border-radius: 100px; 571 | position: absolute; 572 | top: 18px; 573 | right: 7px; 574 | -webkit-transition: all 0.5s; 575 | -moz-transition: all 0.5s; 576 | -ms-transition: all 0.5s; 577 | transition: all 0.5s; 578 | } 579 | 580 | .custom-toggle .bar:first-child { 581 | -webkit-transform: translateY(-6px); 582 | -moz-transform: translateY(-6px); 583 | -ms-transform: translateY(-6px); 584 | transform: translateY(-6px); 585 | } 586 | 587 | .custom-toggle.x .bar { 588 | -webkit-transform: rotate(45deg); 589 | -moz-transform: rotate(45deg); 590 | -ms-transform: rotate(45deg); 591 | transform: rotate(45deg); 592 | } 593 | 594 | .custom-toggle.x .bar:first-child { 595 | -webkit-transform: rotate(-45deg); 596 | -moz-transform: rotate(-45deg); 597 | -ms-transform: rotate(-45deg); 598 | transform: rotate(-45deg); 599 | } 600 | 601 | @media (max-width: 47.999em) { 602 | 603 | .custom-menu-3 { 604 | text-align: left; 605 | } 606 | 607 | .custom-toggle { 608 | display: block; 609 | } 610 | 611 | } 612 | 613 | .menu-item { 614 | color:white; 615 | font-family: 'Roboto', sans-serif; 616 | } 617 | 618 | .l-box { 619 | color:white; 620 | padding-right: 2cm; 621 | height: 2.5em; 622 | font-size: 18px; 623 | font-family: 'Roboto', sans-serif; 624 | } 625 | 626 | .menu-links a:hover { 627 | background-color: #5f66f8; 628 | /*text-decoration: underline;*/ 629 | } 630 | 631 | $primary-color: #03a9f4; 632 | $gray: #ccc; 633 | 634 | *, 635 | *:after, 636 | *:before { 637 | box-sizing: border-box; 638 | } 639 | 640 | body { 641 | font-family: 'Nobile', sans-serif; 642 | } 643 | 644 | .demo-container { 645 | padding: 40px; 646 | width: 100%; 647 | 648 | h1 { 649 | border-bottom: 1px solid $gray; 650 | color: $primary-color; 651 | margin-bottom: 40px; 652 | padding-bottom: 40px; 653 | text-align: center; 654 | } 655 | 656 | h2 { 657 | color: $gray; 658 | } 659 | } 660 | 661 | .react-sanfona { 662 | border: 1px solid $gray; 663 | border-radius: 3px; 664 | margin-bottom: 70px; 665 | } 666 | 667 | .react-sanfona-item { 668 | 669 | &-title { 670 | background-color: #fafafa; 671 | border-top: 1px solid $gray; 672 | color: #333; 673 | padding: 20px; 674 | text-transform: uppercase; 675 | transition: background-color .3s; 676 | } 677 | 678 | &:first-child &-title { 679 | border-top: none; 680 | } 681 | 682 | &-expanded &-title { 683 | background-color: $primary-color; 684 | color: #fff; 685 | 686 | .title-done-btn { 687 | display: inline-block; 688 | float: right; 689 | color: #000; 690 | } 691 | } 692 | 693 | &-body-wrapper { 694 | color: #666; 695 | padding: 20px; 696 | position: relative; 697 | 698 | &:hover .tooltip { 699 | opacity: 1; 700 | } 701 | } 702 | 703 | img { 704 | display: block; 705 | max-width: 100%; 706 | } 707 | 708 | .title-done-btn { 709 | display: none; 710 | } 711 | 712 | } 713 | 714 | .tooltip { 715 | background-color: #000; 716 | bottom: -10px; 717 | border-radius: 3px; 718 | color: #fff; 719 | left: 10px; 720 | opacity: 0; 721 | padding: 10px; 722 | position: absolute; 723 | transition: opacity .3s; 724 | } 725 | 726 | /* Style the buttons that are used to open and close the accordion panel */ 727 | button.accordion { 728 | background-color: #eee; 729 | color: #444; 730 | cursor: pointer; 731 | padding: 18px; 732 | width: 100%; 733 | text-align: left; 734 | border: none; 735 | outline: none; 736 | transition: 0.4s; 737 | } 738 | 739 | /* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */ 740 | button.accordion.active, button.accordion:hover{ 741 | background-color: #ddd; 742 | } 743 | 744 | div.user-liked:hover{ 745 | background-color: #eee; 746 | transform: scale(1.1); 747 | } 748 | 749 | 750 | /* Style the accordion panel. Note: hidden by default */ 751 | div.panel { 752 | padding: 0 10px 0 10px; 753 | background-color: white; 754 | max-height: 0; 755 | overflow: scroll; 756 | transition: 0.4s ease-in-out; 757 | opacity: 0; 758 | 759 | } 760 | 761 | div.panel.show { 762 | opacity: 1; 763 | max-height: 300px; 764 | } 765 | 766 | button.accordion:after { 767 | content: '\02795'; /* Unicode character for "plus" sign (+) */ 768 | font-size: 13px; 769 | color: #777; 770 | float: right; 771 | margin-left: 5px; 772 | } 773 | 774 | button.accordion.active:after { 775 | content: "\2796"; /* Unicode character for "minus" sign (-) */ 776 | } 777 | 778 | .cardstack { 779 | width: 500px; 780 | } 781 | 782 | .card { 783 | color: white; 784 | padding-left: 20px; 785 | } 786 | 787 | .user-liked { 788 | padding-top: 1px; 789 | padding-bottom: 1px; 790 | padding-left: 20px; 791 | vertical-align: middle; 792 | position:relative; 793 | transition: all .2s ease-in-out; 794 | 795 | } 796 | 797 | .user-liked-username { 798 | display:inline-block; 799 | } 800 | 801 | img.userPhoto { 802 | border-radius: 100px; 803 | width: 50px; 804 | height: 50px; 805 | display:inline-block; 806 | float:left; 807 | margin-right:10px; 808 | margin-top: 5px; 809 | } 810 | 811 | .chat-button { 812 | float:right; 813 | display:inline-block; 814 | margin-top:12px; 815 | margin-bottom:1px; 816 | margin-right: 18px; 817 | background-color: #464ef7; 818 | color:white; 819 | } 820 | 821 | /* ============ Notification System ============ */ 822 | .NotifySystemMenu { 823 | position: fixed; 824 | z-index: 2; 825 | right: 10%; 826 | top: 2.5em; 827 | width: 40%; 828 | background-color: white; 829 | box-shadow: 5px 7px 15px -6px; 830 | } 831 | 832 | .notification-read, 833 | .notification-unread span { 834 | letter-spacing: 0em; 835 | } 836 | 837 | .notification-read, 838 | .notification-unread { 839 | position: relative; 840 | padding: 5px; 841 | } 842 | 843 | .notification-read:hover > .deleteButton, 844 | .notification-unread:hover > .deleteButton { 845 | display: block; 846 | } 847 | 848 | .notification-read > .deleteButton, 849 | .notification-unread > .deleteButton { 850 | display: none; 851 | } 852 | 853 | .notify-timeCreated { 854 | font-style: italic; 855 | font-size: 1em; 856 | } 857 | 858 | .notify-description { 859 | font-size: 1.1em; 860 | } 861 | 862 | .notification-read { 863 | background-color: #fff; 864 | } 865 | 866 | .notification-unread { 867 | background-color: #aaa; 868 | } 869 | 870 | .deleteButton { 871 | position: absolute; 872 | top: 2px; 873 | right: 2px; 874 | padding: 2px; 875 | } 876 | 877 | .deleteButton:hover { 878 | background-color: white; 879 | cursor: pointer; 880 | } 881 | 882 | .a-button:hover { 883 | cursor: pointer; 884 | } 885 | 886 | /* ========== Chat ========== */ 887 | 888 | .messageBar { 889 | text-align: left; 890 | width: 100%; 891 | border: 0px; 892 | height: 25px; 893 | } 894 | 895 | .messages { 896 | border-collapse: collapse; 897 | width: 100%; 898 | padding: 5px; 899 | } 900 | 901 | .message { 902 | width: 100%; 903 | border: solid; 904 | border-color: #e6e6ff; 905 | border-width: 1px 0; 906 | } 907 | 908 | 909 | .messagesNotSend { 910 | height: 30%; 911 | } 912 | 913 | .chat-body { 914 | } 915 | 916 | .chat-one { 917 | width: 100%; 918 | 919 | } 920 | 921 | .chat-name { 922 | text-align; left; 923 | } 924 | 925 | .chat-time { 926 | color: gray; 927 | float: right; 928 | } 929 | 930 | .chat-message { 931 | text-align: left; 932 | } 933 | 934 | @media only screen and (max-width: 768px){ 935 | 936 | } 937 | 938 | .new-repo { 939 | margin-left: -50%; 940 | margin-bottom: 3%; 941 | } 942 | 943 | -------------------------------------------------------------------------------- /client/utils.js: -------------------------------------------------------------------------------- 1 | 2 | const skills = ["JavaScript", "React", "Angular.js", "Redux", "Mithril", "Backbone", "Node.js", "Express", "Git", "Passport", "Socket.io", "Mongo", "Mongoose", "Test Driven Development", "Continuous Deployment", "Agile Methodology", "Waterfall Methodology", "OAuth", "PHP", "Postgress", "KNEX", "Browserify", "Webpack", "Grunt", "Gulp", "CSS", "HTML", "ES2015", "React Native", "React-Router", "C++", "Java", "Ruby", "Python", "Go", "Haskell", "Android", "iOS", "C#", "Machine Language(s)", "Ruby on Rails", "MEAN stack", "PERRN stack", "Heroku"] 3 | 4 | // Adds up all skills that the user and project have in common and returns the number 5 | export function getCommonSkillCount(user, project){ 6 | let count = 0; 7 | for(let i = 0; i < user.skills.length; i++){ 8 | for(let j = 0; j < project.req_skills.length; j++){ 9 | if(user.skills[i] === project.req_skills[j]){ 10 | count++; 11 | } 12 | } 13 | } 14 | return count; 15 | } 16 | 17 | export function getSkills() { 18 | return skills; 19 | } 20 | 21 | export function convertTimeToString(time) { 22 | var now = new Date(); 23 | time = new Date(time); 24 | var years = now.getFullYear() - time.getFullYear(); 25 | var months = now.getMonth() - time.getMonth(); 26 | var days = now.getDate() - time.getDate(); 27 | var hours = now.getHours() - time.getHours(); 28 | var minutes = now.getMinutes() - time.getMinutes(); 29 | 30 | if(years > 0){ 31 | return years === 1 ? "1 year ago" : years + " years ago"; 32 | } 33 | if(months > 0){ 34 | return months === 1 ? "1 month ago" : months + " months ago"; 35 | } 36 | if(days > 0){ 37 | return days === 1 ? "a day ago" : days + " days ago"; 38 | } 39 | if(hours > 0){ 40 | return hours === 1 ? "1 hour ago" : hours + " hours ago"; 41 | } 42 | if(minutes > 0){ 43 | return minutes === 1 ? "1 minute ago" : minutes + " minutes ago"; 44 | } 45 | return "a few moments ago"; 46 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gitluv", 3 | "version": "1.0.0", 4 | "description": "Imagine being a lonely developer who is really freakin good at ReactNative but no one knows how awesome it is yet, and so you can't find any work for it. Imagine having the next Big Idea, but it would require this crazy new language ReactNative that you just heard about but no one knows how to code in. Imagine GitLuv making all these problems go away. Simply by logging into GitLuv with your Github credentials, any member of the GIthub comminuty can connect with one another in search of just what they need.", 5 | "scripts": { 6 | "start": "nodemon server/index.js", 7 | "lint": "node ./node_modules/eslint/bin/eslint.js ./client", 8 | "test": "./test/run.sh" 9 | }, 10 | "devDependencies": { 11 | "babel-core": "^6.0.20", 12 | "babel-eslint": "^4.1.3", 13 | "babel-loader": "^6.2.4", 14 | "babel-preset-es2015": "^6.0.15", 15 | "babel-preset-react": "^6.0.15", 16 | "babel-preset-stage-0": "^6.0.15", 17 | "eslint": "^3.1.1", 18 | "eslint-config-airbnb": "^9.0.1", 19 | "eslint-plugin-import": "^1.11.1", 20 | "eslint-plugin-jsx-a11y": "^1.5.5", 21 | "eslint-plugin-react": "^5.2.2", 22 | "react-tap-event-plugin": "^1.0.0", 23 | "webpack": "^1.13.1" 24 | }, 25 | "dependencies": { 26 | "animate.css": "^3.5.1", 27 | "babel-preset-es2015": "^6.0.15", 28 | "babel-preset-react": "^6.0.15", 29 | "babel-preset-stage-0": "^6.0.15", 30 | "babelify": "^7.3.0", 31 | "body-parser": "^1.15.2", 32 | "browserify-middleware": "^7.0.0", 33 | "chai": "^3.5.0", 34 | "delightful-cookies": "^1.2.0", 35 | "es6-promise": "^3.2.1", 36 | "eslint": "^3.2.2", 37 | "express": "^4.14.0", 38 | "isomorphic-fetch": "^2.2.1", 39 | "jquery": "^3.1.0", 40 | "middleware": "^1.0.0", 41 | "mocha": "^2.5.3", 42 | "moment": "^2.14.1", 43 | "mongo": "^0.1.0", 44 | "mongoose": "^4.5.7", 45 | "node-fetch": "^1.6.0", 46 | "node-wit": "^4.0.0", 47 | "nodemon": "^1.10.0", 48 | "path": "^0.12.7", 49 | "react": "^15.1.0", 50 | "react-addons-transition-group": "^15.2.1", 51 | "react-cardstack": "^0.1.1", 52 | "react-dom": "^15.0.1", 53 | "react-popout": "^0.5.3", 54 | "react-redux": "^4.4.5", 55 | "react-router": "^2.5.1", 56 | "react-sanfona": "0.0.14", 57 | "react-sidebar": "^2.2.1", 58 | "redux": "^3.5.2", 59 | "redux-logger": "^2.6.1", 60 | "request": "^2.74.0", 61 | "socket.io": "^1.4.8", 62 | "socket.io-client": "^1.4.8", 63 | "supertest": "^1.2.0", 64 | "supertest-as-promised": "^3.2.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /server/apis/github-api.js: -------------------------------------------------------------------------------- 1 | var fetch = require('isomorphic-fetch'); 2 | var Profile = module.exports 3 | 4 | //function that gets user data from github based on a user's authToken 5 | Profile.getUserData = function(authToken) { 6 | return fetch('https://api.github.com/user', { 7 | method: 'GET', 8 | headers: { 9 | Authorization: "token " + authToken, 10 | Accept: 'application/json' 11 | } 12 | }) 13 | .then(response => { 14 | return response.json(); 15 | }) 16 | } -------------------------------------------------------------------------------- /server/db.js: -------------------------------------------------------------------------------- 1 | //File in place to create one and only one connection to the database. 2 | 3 | var mongoose = require('mongoose'); 4 | mongoose.connect('mongodb://gitluv:lolboi5@ds031965.mlab.com:31965/gitluv'); 5 | 6 | module.exports = mongoose; -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var browserify = require('browserify-middleware'); 3 | var path = require('path'); 4 | var fetch = require('isomorphic-fetch'); 5 | var bodyParser = require('body-parser'); 6 | var mongoose = require('mongoose'); 7 | var path = require('path') 8 | 9 | var Project = require('./models/project') 10 | var User = require('./models/user'); 11 | var Chat = require('./models/chat') 12 | var Notify = require('./models/notifications'); 13 | var Auth = require('./models/util') 14 | 15 | //code to start express.js 16 | var app = express(); 17 | exports.app = app 18 | var server = app.listen(4000); 19 | var io = require('socket.io').listen(server) 20 | var assetFolder = path.join(__dirname, '..', 'client','public'); 21 | 22 | // Serve Static Assets 23 | app.use(express.static(assetFolder)); 24 | app.use(bodyParser.json()); 25 | 26 | app.use(function(req, res, next) { 27 | res.header("Access-Control-Allow-Origin", "*"); 28 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 29 | next(); 30 | }); 31 | 32 | // Serve JS Assets 33 | app.get('/app-bundle.js', 34 | browserify('./client/index.js', { 35 | transform: [ [ require('babelify'), { presets: ['es2015', 'react'] } ] ] 36 | }) 37 | ); 38 | 39 | 40 | // 41 | // Github Authorization 42 | // 43 | 44 | var Profile = require('./apis/github-api'); 45 | var cookie = null; 46 | 47 | app.get('/auth/login', (req, res) => { 48 | 49 | fetch('https://github.com/login/oauth/access_token?client_id=444a46dcbe1340ce4a49&client_secret=df1f3fc9a5da7f88c06a4432302c42d04ac8f151&code=' + req.param('code'), { 50 | method: 'POST', 51 | headers: { 52 | Accept: 'application/json' 53 | } 54 | }) 55 | .then(response => { 56 | return response.json() 57 | }) 58 | .then(result => { 59 | cookie = result.access_token; 60 | return Profile.getUserData(result.access_token) 61 | }) 62 | .then(data => { 63 | //'/api/users/:username' 64 | //User.getUser(req.params.username) 65 | //if exists send to '/swipe' endpoint 66 | //'/api/usersPOST' 67 | //if not exist send to '/skills' endpoint 68 | User.getUser(data.login) 69 | .then(userData => { 70 | if(!userData){ 71 | 72 | //store user in DB 73 | var userStuff = { 74 | username: data.login, 75 | avatar_url: data.avatar_url, 76 | url: data.html_url, 77 | location: data.location, 78 | bio: data.bio, 79 | followers: data.followers, 80 | updated_at: data.updated_at 81 | } 82 | User.createIfNotExists( userStuff ) 83 | Notify.add({ 84 | description: "Welcome to GitLuv!", 85 | username: data.login, 86 | }) 87 | res.cookie("AuthToken", cookie) 88 | res.redirect('/skills'); 89 | } else { 90 | res.cookie("AuthToken", cookie) 91 | res.redirect('/swipe'); 92 | } 93 | }) 94 | }) 95 | }); 96 | 97 | 98 | // 99 | // Project API 100 | // 101 | 102 | app.use('/api/projectsGET', Auth.isAuthenticated, function (req, res) { 103 | 104 | Project.all() 105 | .then(function (projects) { 106 | console.log("getting!!: ", projects) 107 | res.status(200).send(projects) 108 | }) 109 | .catch(function (err) { 110 | console.log("Project.all error: ", err) 111 | res.status(500).send(err) 112 | }) 113 | }) 114 | 115 | app.use('/api/projects/:title', Auth.isAuthenticated, function (req, res) { 116 | 117 | Project.getProject(req.params.title) 118 | .then(function(project){ 119 | res.status(200).send(project) 120 | }) 121 | .catch(function (err){ 122 | console.log("Project.getProject error: ", err) 123 | res.status(500).send(err) 124 | }) 125 | }) 126 | 127 | 128 | app.use('/api/projectsPOST', Auth.isAuthenticated, function (req, res) { 129 | Project.createIfNotExists( req.body ) 130 | res.sendStatus(201) 131 | }) 132 | 133 | app.use('/api/projectsPATCH', Auth.isAuthenticated, function (req, res) { 134 | 135 | //This function takes a 2 piece array, first index is the title and 136 | //the second is an object of all information being changed. 137 | 138 | console.log('lolwut ' + JSON.stringify(req.body)) 139 | 140 | Project.editProject(req.body[0], req.body[1]) 141 | .then(x => res.sendStatus(201)) 142 | .catch(function(err){ 143 | console.log("Project.updateProject error: ", err) 144 | res.sendStatus(500) 145 | }) 146 | }) 147 | 148 | 149 | // 150 | // Users API 151 | // 152 | 153 | app.use('/api/usersGET', Auth.isAuthenticated, function (req, res) { 154 | User.all() 155 | .then(function (users) { 156 | res.status(200).send(users) 157 | }) 158 | .catch(function (err) { 159 | console.log("Users.all error:", err) 160 | res.status(500).send(err) 161 | }) 162 | }) 163 | 164 | app.use('/api/users/:username', Auth.isAuthenticated, function (req, res) { 165 | 166 | User.getUser(req.params.username) 167 | .then(function(user){ 168 | res.status(200).send(user) 169 | }) 170 | .catch(function (err){ 171 | console.log("Users.getUser error: ", err) 172 | res.status(500).send(err) 173 | }) 174 | }) 175 | 176 | 177 | app.use('/api/usersPOST', Auth.isAuthenticated, function (req, res) { 178 | 179 | User.createIfNotExists( req.body ) 180 | res.sendStatus(201) 181 | }) 182 | 183 | app.use('/api/usersPATCH', Auth.isAuthenticated, function (req, res) { 184 | 185 | //This function takes a 2 piece array, first index is the username and 186 | //the second is an object of all information being changed. 187 | 188 | User.editUser(req.body[0], req.body[1]) 189 | .then(x => res.sendStatus(201)) 190 | .catch(function(err){ 191 | console.log("User.updateUser error: ", err) 192 | res.sendStatus(500) 193 | }) 194 | }) 195 | 196 | // 197 | // Notifications API 198 | // 199 | 200 | app.delete('/api/notifications', (req, res) => { 201 | Notify.remove(req.body); 202 | res.send({}); 203 | }) 204 | 205 | app.post('/api/notifications', (req, res) => { 206 | Notify.add(req.body); 207 | res.send({}); 208 | }) 209 | 210 | app.get('/api/notifications/:username', (req, res) => { 211 | Notify.get(req.params.username) 212 | .then(data => { 213 | res.send(data); 214 | }) 215 | }) 216 | 217 | app.get('/api/unreadNotifications/:username', (req, res) => { 218 | Notify.getUnread(req.params.username) 219 | .then(data => { 220 | res.send(data); 221 | }) 222 | }) 223 | 224 | app.get('/api/getNotifications/:id', (req, res) => { 225 | console.log(req.params.id) 226 | Notify.getOne(req.params.id) 227 | .then(data => { 228 | res.send(data); 229 | }) 230 | }) 231 | 232 | app.patch('/api/notifications', (req, res) => { 233 | Notify.read(req.body.id) 234 | res.send({}) 235 | }) 236 | 237 | 238 | // 239 | // Chat API 240 | // 241 | 242 | app.use('/api/chatGET', Auth.isAuthenticated, function (req, res) { 243 | 244 | Chat.all() 245 | .then(function (chats) { 246 | res.status(200).send(chats) 247 | }) 248 | .catch(function (err) { 249 | console.log("Chat.all error:", err) 250 | res.status(500).send(err) 251 | }) 252 | }) 253 | 254 | app.use('/api/chat/:chatRoom', Auth.isAuthenticated, function (req, res) { 255 | 256 | 257 | Chat.getChatroom(req.params.chatRoom) 258 | .then(function(room){ 259 | res.status(200).send(room) 260 | }) 261 | .catch(function(err){ 262 | console.log("Chat.getChatroom error: ", err) 263 | res.sendStatus(500) 264 | }) 265 | }) 266 | 267 | app.use('/api/chatPOST', Auth.isAuthenticated, function (req, res) { 268 | 269 | Chat.createIfNotExists( req.body ) 270 | res.sendStatus(201) 271 | }) 272 | 273 | app.use('/api/chatPATCH', Auth.isAuthenticated, function (req, res) { 274 | 275 | Chat.updateChatroom(req.body[0], req.body[1]) 276 | .then(x => res.sendStatus(201)) 277 | .catch(function(err){ 278 | console.log("Chat.updateChatroom error: ", err) 279 | }) 280 | }) 281 | 282 | 283 | // 284 | // Chat Sockets 285 | // 286 | 287 | io.on('connection', function(socket){ 288 | 289 | //subscribe functionality triggered upon entering messages tab. 290 | socket.on('subscribe', function(room) { 291 | console.log('joining room', room); 292 | socket.join(room); 293 | }) 294 | 295 | //unsubscribe functionality triggered upon leaving a room. 296 | socket.on('unsubscribe', function(room) { 297 | console.log('leaving room', room); 298 | socket.leave(room); 299 | }) 300 | 301 | //send functionality when someone submits a message. 302 | socket.on('send', function(data) { 303 | 'use strict' 304 | 305 | let roomSub = data.room 306 | 307 | if(data.message){ 308 | 309 | Notify.add({ 310 | description: `New message from ${data.sentBy}: ${data.message}`, 311 | username: data.room.split(data.sentBy).filter(element => element)[0], 312 | }) 313 | 314 | Chat.updateChatroom(data.room, {messages: [data]}) 315 | .then(function(x){ 316 | console.log("in '.then()' roomName: " + roomSub + " data: " + data) 317 | data.room = roomSub 318 | io.in(data.room).emit('chat message', data); 319 | }) 320 | } 321 | }); 322 | }) 323 | 324 | 325 | // Wild card route for client side routing. 326 | app.get('/*', function(req, res){ 327 | res.sendFile( assetFolder + '/index.html' ); 328 | }) 329 | 330 | // 331 | // Static assets (html, etc.) 332 | // 333 | var assetFolder = path.resolve(__dirname, '../client/public') 334 | var apiFolder = path.resolve(__dirname, './apis') 335 | 336 | // var port = process.env.PORT || 4000 337 | // app.listen(port) 338 | // console.log("Listening on port", port) 339 | -------------------------------------------------------------------------------- /server/models/chat.js: -------------------------------------------------------------------------------- 1 | var Chat = module.exports; 2 | var mongoose = require('../db'); 3 | mongoose.Promise = global.Promise; 4 | var Schema = mongoose.Schema; 5 | 6 | var conn = mongoose.connection; 7 | conn.on('error', console.error.bind(console, 'connection error in chat:')); 8 | 9 | conn.once('open', function() { 10 | console.log("chat running!!!") 11 | }) 12 | 13 | var chatSchema = new Schema({ 14 | chatRoom : String, 15 | visionary: String, 16 | developer: String, 17 | messages : Array, 18 | initiated: Boolean, 19 | time : String 20 | }) 21 | 22 | var ChatCollection = mongoose.model('Chatcollection', chatSchema) 23 | 24 | //function that creates a new chatroom, updates if it exists. 25 | Chat.createIfNotExists = function(attrs){ 26 | 27 | let chatRoom = attrs.chatRoom 28 | delete attrs.chatRoom 29 | 30 | return ChatCollection.findOneAndUpdate({chatRoom: chatRoom}, attrs, {upsert: true}, function (err, doc) { 31 | if(err){ 32 | console.log("!!!-----------------!!!", err) 33 | } 34 | }) 35 | } 36 | 37 | //function that returns all chatrooms 38 | Chat.all = function(){ 39 | 40 | return ChatCollection.find(function (err, chats) { 41 | if(err) console.log("!!!------------!!!", err) 42 | }) 43 | } 44 | 45 | //function that gets a chatroom by name 46 | Chat.getChatroom = function(chatRoom){ 47 | 48 | return ChatCollection.findOne({chatRoom: chatRoom}, function (err, doc) { 49 | if(err) console.log("!!!-----------------!!!", err) 50 | }) 51 | } 52 | 53 | //function that adds a message to a chatroom. Since parts of the collection are stored 54 | //in an array, the chatroom is first gotten from the database and then the new 55 | //message is then concatenated 56 | Chat.updateChatroom = function(chatRoom, changedAttrs){ 57 | 58 | return Chat.getChatroom(chatRoom) 59 | .then( function (chatRoomInfo){ 60 | 61 | let cRoom = changedAttrs.room 62 | if(changedAttrs.messages[0].message){ 63 | let newMess = changedAttrs.messages[0] 64 | cRoom = newMess.room 65 | delete newMess.room 66 | changedAttrs.messages = chatRoomInfo.messages.concat([newMess]) 67 | } 68 | 69 | return ChatCollection.findOneAndUpdate({chatRoom: cRoom}, changedAttrs, function (err, doc) { 70 | if(err){ 71 | console.log("!!!-----------------!!!", err) 72 | } 73 | }) 74 | }) 75 | .catch(err => console.log("Chat.updateChatroom error: ", err)) 76 | } -------------------------------------------------------------------------------- /server/models/notifications.js: -------------------------------------------------------------------------------- 1 | var Notify = module.exports; 2 | var mongoose = require('../db'); 3 | mongoose.Promise = global.Promise; 4 | var Schema = mongoose.Schema; 5 | 6 | var conn = mongoose.connection; 7 | 8 | var notificationSchema = new Schema({ 9 | description: String, 10 | isRead: Boolean, 11 | username: String, 12 | created: String, 13 | }) 14 | 15 | var Collection = mongoose.model('Notifications', notificationSchema); 16 | 17 | Notify.add = function(obj) { 18 | var item = new Collection(obj) 19 | item.created = new Date(); 20 | if(!obj.isRead){ 21 | item.isRead = false; 22 | } else { 23 | item.isRead = obj.isRead; 24 | } 25 | 26 | item.save(err => { 27 | if(err) 28 | console.log("ERROR notifications.js:21", err) 29 | }) 30 | } 31 | 32 | Notify.remove = function(obj) { 33 | Collection.find({ _id: obj.id }).remove().exec(); 34 | } 35 | 36 | Notify.getOne = function(id) { 37 | return Collection.find({ _id: id }) 38 | } 39 | 40 | Notify.get = function(username) { 41 | return Collection.find({ username: username }) 42 | } 43 | 44 | Notify.getUnread = function(username) { 45 | return Collection.find({ username: username }).where('isRead').equals(false) 46 | } 47 | 48 | Notify.read = function(id) { 49 | return Collection.findOneAndUpdate({ _id: id }, { isRead: true }, { upsert: true }, function(err, docs){ 50 | if(err) console.log("ERROR! Server notifications.js line 50", err); 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /server/models/project.js: -------------------------------------------------------------------------------- 1 | var Project = module.exports; 2 | var mongoose = require('../db'); 3 | mongoose.Promise = global.Promise; 4 | var Schema = mongoose.Schema; 5 | 6 | var conn = mongoose.connection; 7 | conn.on('error', console.error.bind(console, 'connection error:')); 8 | 9 | conn.once('open', function() { 10 | console.log("project running!!!") 11 | }); 12 | 13 | var projectSchema = new Schema({ 14 | title: String, 15 | username: String, 16 | repo_url: String, 17 | description: String, 18 | location: String, 19 | looking_for: String, 20 | req_skills: Array, 21 | users_liked: Array, 22 | users_disliked: Array, 23 | }) 24 | 25 | var ProjectCollection = mongoose.model('ProjectCollection', projectSchema) 26 | 27 | //function that creates and project and updates if it exists 28 | Project.createIfNotExists = function(attrs){ 29 | 30 | let title = attrs.title 31 | delete attrs.title 32 | 33 | return ProjectCollection.findOneAndUpdate({title: title}, attrs, {upsert: true}, function (err, doc) { 34 | if(err){ 35 | console.log("!!!-----------------!!!", err) 36 | } 37 | }) 38 | } 39 | 40 | //function that returns all projects 41 | Project.all = function(){ 42 | 43 | return ProjectCollection.find(function (err, projects) { 44 | if(err) console.log("!!!-----------------!!!", err) 45 | }) 46 | } 47 | 48 | //function that gets a project by title 49 | Project.getProject = function(projectTitle){ 50 | 51 | return ProjectCollection.findOne({title: projectTitle}, function (err, projects) { 52 | if(err) console.log("!!!-----------------!!!", err) 53 | }) 54 | } 55 | 56 | //function that edits a project. Since parts of the collection are stored 57 | //in an array, the project is first gotten from the database and the arrays 58 | //are then concatenated 59 | Project.editProject = function(title, changedAttrs){ 60 | 61 | return Project.getProject(title) 62 | .then(function (projectInfo){ 63 | 64 | if(changedAttrs.req_skills){ 65 | let newArr = [] 66 | for(let i = 0; i < changedAttrs.req_skills.length; i++){ 67 | if(!(projectInfo.req_skills.indexOf(changedAttrs.req_skills[i]) >= 0)){ 68 | newArr.push(changedAttrs.req_skills[i]) 69 | } 70 | } 71 | changedAttrs.req_skills = projectInfo.req_skills.concat(newArr) 72 | } 73 | 74 | if(changedAttrs.users_liked){ 75 | let newArr2 = [] 76 | for(let i = 0; i < changedAttrs.users_liked.length; i++){ 77 | if(!(projectInfo.users_liked.indexOf(changedAttrs.users_liked[i]) >= 0)){ 78 | newArr2.push(changedAttrs.users_liked[i]) 79 | } 80 | } 81 | changedAttrs.users_liked = projectInfo.users_liked.concat(newArr2) 82 | } 83 | 84 | if(changedAttrs.users_disliked){ 85 | let newArr3 = [] 86 | for(let i = 0; i < changedAttrs.users_disliked.length; i++){ 87 | if(!(projectInfo.users_disliked.indexOf(changedAttrs.users_disliked[i]) >= 0)){ 88 | newArr3.push(changedAttrs.users_disliked[i]) 89 | } 90 | } 91 | changedAttrs.users_disliked = projectInfo.users_disliked.concat(newArr3) 92 | } 93 | 94 | return ProjectCollection.findOneAndUpdate({title: title}, changedAttrs, function (err, doc) { 95 | if(err){ 96 | console.log("!!!-----------------!!!", err) 97 | } 98 | }) 99 | }) 100 | .catch(err => console.log("Project.editProject error: ", err)) 101 | } -------------------------------------------------------------------------------- /server/models/user.js: -------------------------------------------------------------------------------- 1 | var User = module.exports 2 | var mongoose = require('../db'); 3 | mongoose.Promise = global.Promise 4 | var Schema = mongoose.Schema; 5 | 6 | var conn = mongoose.connection; 7 | conn.on('error', console.error.bind(console, 'connection error:')); 8 | 9 | conn.once('open', function() { 10 | console.log("running!!!") 11 | }); 12 | 13 | var userSchema = new Schema({ 14 | username: String, 15 | avatar_url: String, 16 | url: String, 17 | location: String, 18 | bio: String, 19 | followers: Number, 20 | skills: Array, 21 | visionary: Boolean, 22 | projects: Array, 23 | endorsements: Array, 24 | updated_at: String 25 | }) 26 | 27 | var UserCollection = mongoose.model('UserCollection', userSchema) 28 | 29 | //function that creates a user, will update if it already exists 30 | User.createIfNotExists = function(attrs){ 31 | 32 | let usrnm = attrs.username 33 | delete attrs.username 34 | 35 | return UserCollection.findOneAndUpdate({username: usrnm}, attrs, {upsert: true}, function (err, doc) { 36 | if(err){ 37 | console.log("!!!-----------------!!!", err) 38 | } 39 | }) 40 | } 41 | 42 | //function that gets all users 43 | User.all = function(){ 44 | 45 | return UserCollection.find(function (err, users) { 46 | if(err) console.log("!!!-----------------!!!", err) 47 | }) 48 | } 49 | 50 | //function that gets a user by username 51 | User.getUser = function(username){ 52 | 53 | return UserCollection.findOne({username: username}, function (err, projects) { 54 | if(err) console.log("!!!-----------------!!!", err) 55 | }) 56 | } 57 | 58 | //function that edits a user. Since parts of the collection are stored 59 | //in an array, the User is first gotten from the database and the arrays 60 | //are then concatenated 61 | User.editUser = function(username, changedAttrs){ 62 | 63 | return User.getUser(username) 64 | .then(function (userInfo){ 65 | 66 | if(changedAttrs.skills){ 67 | let newArr = [] 68 | for(let i = 0; i < changedAttrs.skills.length; i++){ 69 | if(!(userInfo.skills.indexOf(changedAttrs.skills[i]) >= 0)){ 70 | newArr.push(changedAttrs.skills[i]) 71 | } 72 | } 73 | changedAttrs.skills = userInfo.skills.concat(newArr) 74 | } 75 | 76 | if(changedAttrs.projects){ 77 | let newArr2 = [] 78 | for(let i = 0; i < changedAttrs.projects.length; i++){ 79 | if(!(userInfo.projects.indexOf(changedAttrs.projects[i]) >= 0)){ 80 | newArr2.push(changedAttrs.projects[i]) 81 | } 82 | } 83 | changedAttrs.projects = userInfo.projects.concat(newArr2) 84 | } 85 | 86 | return UserCollection.findOneAndUpdate({username: username}, changedAttrs, function (err, doc) { 87 | if(err){ 88 | console.log("!!!-----------------!!!", err) 89 | } 90 | }) 91 | }) 92 | .catch(err => console.log("User.editUser error: ", err)) 93 | } -------------------------------------------------------------------------------- /server/models/util.js: -------------------------------------------------------------------------------- 1 | 2 | var Auth = module.exports; 3 | var express = require('express'); 4 | var fetch = require('isomorphic-fetch'); 5 | var app = express(); 6 | 7 | // 8 | // Github Authorization 9 | // 10 | //set up middleware to check 'isAuthenticate' on protected endpoints 11 | 12 | Auth.isAuthenticated = function(req, res, next) { 13 | // console.log('sldfjalfkj', req.get('Authorization')) 14 | // Check for Authorization header in req.get('Authorization') 15 | var authToken = req.get('Authorization') 16 | if(!authToken){ 17 | res.send(401); 18 | } 19 | else{ 20 | // if exists then fetch data from github 21 | fetch('https://api.github.com/user', { 22 | method: 'GET', 23 | headers: { 24 | Authorization: "token " + authToken, 25 | Accept: 'application/json' 26 | } 27 | }) 28 | .then(function(data){ 29 | // console.log('cookie test???', document.cookie.split('')) 30 | // If there is data run next() 31 | if(data.status === 200 || data.statusText === 'Authorized'){ 32 | console.log('made it') 33 | return next(); 34 | } 35 | else{ 36 | // else REDIRECT 37 | res.redirect('/'); 38 | } 39 | }) 40 | } 41 | } -------------------------------------------------------------------------------- /test/bootstrap.js: -------------------------------------------------------------------------------- 1 | // 2 | // This code is to allow all test files to easily require 3 | // the test helper file, regardless of nested folder location. 4 | // 5 | // e.g. require(TEST_HELPER) 6 | // 7 | global.TEST_HELPER = __dirname + '/test-helper.js' 8 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | ./node_modules/.bin/mocha --recursive -r test/bootstrap.js "$@" 2 | -------------------------------------------------------------------------------- /test/server/apis/projects-api_test.js: -------------------------------------------------------------------------------- 1 | require(TEST_HELPER) 2 | 3 | var request = require('supertest-as-promised') 4 | var routes = require(__server + '/apis/projects-api.js') 5 | 6 | describe("Projects API", function() { 7 | 8 | var app = TestHelper.createApp() 9 | app.use('/', routes) 10 | app.testReady() 11 | 12 | it_("creates and gets all Projects", function * () { 13 | var newProject; 14 | 15 | yield request(app) 16 | .post('/api/projects') 17 | .send({title: 'reactjs', repo_url: 'http://www.github.com/reactjs/reactjs', 18 | description: '', req_skills: [], users_liked: [], users_disliked: []}) 19 | .expect(201) 20 | 21 | yield request(app) 22 | .get('/api/projects') 23 | .expect(200) 24 | .expect(function (response) { 25 | var projects = response.body 26 | expect( projects.length ).to.equal(1) 27 | expect( projects[0].title ).to.equal('reactjs') 28 | expect( projects[0].repo_url ).to.equal('http://www.github.com/reactjs/reactjs') 29 | expect( projects[0].description ).to.equal('') 30 | }) 31 | }) 32 | 33 | it_("gets a Project by title", function * () { 34 | 35 | yield request(app) 36 | .get('/api/projects/reactjs') 37 | .expect(200) 38 | .expect(function (response) { 39 | var projects = response.body 40 | expect( projects.title ).to.equal('reactjs') 41 | expect( projects.repo_url ).to.equal('http://www.github.com/reactjs/reactjs') 42 | expect( projects.description ).to.equal('') 43 | }) 44 | }) 45 | 46 | it_("edits a project", function * () { 47 | 48 | yield request(app) 49 | .patch('/api/projects') 50 | .send(['reactjs', {description: 'sick!!!', users_liked: ['mccarthyist']}]) 51 | .expect(201) 52 | }) 53 | }) -------------------------------------------------------------------------------- /test/server/apis/users-api_test.js: -------------------------------------------------------------------------------- 1 | require(TEST_HELPER) 2 | 3 | var request = require('supertest-as-promised') 4 | var routes = require(__server + '/apis/users-api.js') 5 | 6 | describe("Users API", function() { 7 | 8 | var app = TestHelper.createApp() 9 | app.use('/', routes) 10 | app.testReady() 11 | 12 | xit_("creates and gets all Users", function * () { 13 | var newUser; 14 | 15 | yield request(app) 16 | .post('/users') 17 | .send({ username: 'mccarthyist', avatar_url: '', url: '', location: '', bio: '', 18 | repos: [], followers: 0, skills: ["react.js", "node.js", "dancing"], visionary: false, updated_at: ''}) 19 | .expect(201) 20 | 21 | yield request(app) 22 | .get('/users') 23 | .expect(200) 24 | .expect(function (response) { 25 | var users = response.body 26 | expect( users.length ).to.equal(1) 27 | expect( users[0].username ).to.equal('mccarthyist' ) 28 | expect( users[0].avatar_url ).to.equal('') 29 | expect( users[0].url ).to.equal('') 30 | expect( users[0].location ).to.equal('') 31 | expect( users[0].bio ).to.equal('') 32 | expect( users[0].followers ).to.equal(0) 33 | expect( users[0].visionary ).to.equal(false) 34 | expect( users[0].updated_at ).to.equal('') 35 | }) 36 | }) 37 | 38 | xit_("gets a User by username", function * () { 39 | 40 | yield request(app) 41 | .get('/users/mccarthyist') 42 | .expect(200) 43 | .expect(function (response) { 44 | var user = response.body 45 | expect( user.username ).to.equal('mccarthyist' ) 46 | expect( user.avatar_url ).to.equal('') 47 | expect( user.url ).to.equal('') 48 | expect( user.location ).to.equal('') 49 | expect( user.bio ).to.equal('') 50 | expect( user.followers ).to.equal(0) 51 | expect( user.visionary ).to.equal(false) 52 | expect( user.updated_at ).to.equal('') 53 | }) 54 | }) 55 | 56 | it_("edits an existing user/pushes new skills", function * () { 57 | yield request(app) 58 | .patch('/users') 59 | .send(['mccarthyist', {skills: ['lol', 'werk', 'react.js'], visionary: true, projects: ['wut', 'duh', 'hek']}]) 60 | .expect(201) 61 | }) 62 | 63 | }) -------------------------------------------------------------------------------- /test/server/models/project_test.js: -------------------------------------------------------------------------------- 1 | require(TEST_HELPER) 2 | 3 | var request = require('supertest-as-promised') 4 | var Project = require(__server + '/models/project.js') 5 | 6 | describe("Project Model", function() { 7 | 8 | // Promise coroutines 9 | it_("creates and persists data", function * () { 10 | var projectAttrs = { title: 'reactjs', repo_url: 'http://www.github.com/reactjs/reactjs', 11 | description: '', req_skills: [], users_liked: [], users_disliked: []} 12 | 13 | Project.createIfNotExists(projectAttrs) 14 | 15 | var allProjects = yield Project.all() 16 | expect( allProjects.length ).to.equal(1) 17 | expect( allProjects[0].title ).to.equal( 'reactjs' ) 18 | 19 | var getProject = yield Project.getProject('reactjs') 20 | expect( getProject.title ).to.be.a('string') 21 | expect( getProject.title ).to.equal('reactjs') 22 | expect( getProject.description ).to.equal('') 23 | }) 24 | }) -------------------------------------------------------------------------------- /test/server/models/user_test.js: -------------------------------------------------------------------------------- 1 | require(TEST_HELPER) 2 | 3 | var request = require('supertest-as-promised') 4 | var User = require(__server + '/models/user.js') 5 | 6 | xdescribe("User Model", function() { 7 | 8 | // Promise coroutines 9 | it_("creates and persists data", function * () { 10 | var userAttrs = { username: 'mccarthyist', avatar_url: '', url: '', 11 | location: '', bio: '', repos: [], followers: 0, skills: [], 12 | visionary: false, updated_at: ''} 13 | 14 | User.createIfNotExists(userAttrs) 15 | 16 | var allUsers = yield User.all() 17 | expect( allUsers.length ).to.equal(1) 18 | expect( allUsers[0].username ).to.equal( 'mccarthyist' ) 19 | 20 | var getUser = yield User.getUser('mccarthyist') 21 | expect( getUser.username ).to.be.a('string') 22 | expect( getUser.username ).to.equal('mccarthyist') 23 | expect( getUser.bio ).to.equal('') 24 | }) 25 | }) -------------------------------------------------------------------------------- /test/test-helper.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = 'test' 2 | 3 | // The following allows you to require files independent of 4 | // the location of your test file. 5 | // Example: 6 | // var User = require(__server + '/models/user.js') 7 | // 8 | global.__server = __dirname + '/../server' 9 | global.__client = __dirname + '/../client' 10 | 11 | // 12 | // Assertions 13 | // 14 | var chai = require('chai') 15 | // Option 1: Make the `expect` function available in every test file 16 | global.expect = chai.expect 17 | // Option 2: Make everything should-able 18 | // global.should = chai.should() 19 | 20 | 21 | // 22 | // Helper Functions 23 | // 24 | // This is the object you can attach any helper functions used across 25 | // several test files. 26 | global.TestHelper = {} 27 | 28 | // 29 | // Mock apps for API testing 30 | // 31 | var express = require('express') 32 | 33 | TestHelper.createApp = function (loader) { 34 | var app = express() 35 | app.use(require('body-parser').json()) 36 | 37 | app.testReady = function () { 38 | // Log all errors 39 | app.use(function (err, req, res, next) { 40 | console.error("==Error==") 41 | console.error(" " + err.stack) 42 | next(err) 43 | }) 44 | } 45 | return app 46 | } 47 | 48 | // 49 | // Mocha "helpers" to support coroutines tests 50 | // 51 | var Bluebird = require('bluebird') 52 | 53 | global.before_ = function (f) { before ( Bluebird.coroutine(f) ) } 54 | global.beforeEach_ = function (f) { beforeEach ( Bluebird.coroutine(f) ) } 55 | global.it_ = function (description, f) { it ( description, Bluebird.coroutine(f) ) } 56 | global.xit_ = function (description, f) { xit ( description, f ) } 57 | global.it_.only = function (description, f) { it.only( description, Bluebird.coroutine(f) ) } 58 | --------------------------------------------------------------------------------