├── .babelrc
├── .gitignore
├── test
├── run.sh
├── bootstrap.js
├── server
│ ├── models
│ │ ├── user_test.js
│ │ └── project_test.js
│ └── apis
│ │ ├── projects-api_test.js
│ │ └── users-api_test.js
└── test-helper.js
├── SUPASUPACHATTY
├── ngrok
├── ngrok.zip
├── package.json
└── messenger.js
├── .jshintrc
├── client
├── actions.js
├── public
│ ├── images
│ │ ├── badge.jpeg
│ │ ├── github.jpeg
│ │ └── visionary_badge.jpeg
│ ├── index.html
│ ├── pureResponsive.css
│ ├── styles.css
│ └── pure.css
├── index.js
├── models
│ ├── profile.js
│ ├── project.js
│ ├── notifications.js
│ ├── users.js
│ ├── projects.js
│ └── chat.js
├── app.js
├── components
│ ├── Landing.js
│ ├── NotifySystem.js
│ ├── Profile.js
│ ├── Messages.js
│ ├── ChatBox.js
│ ├── Skills.js
│ ├── UserProfile.js
│ ├── Sidebar.js
│ ├── Project.js
│ ├── Swipe.js
│ └── CreateProject.js
└── utils.js
├── server
├── db.js
├── apis
│ └── github-api.js
├── models
│ ├── notifications.js
│ ├── util.js
│ ├── chat.js
│ ├── user.js
│ └── project.js
└── index.js
├── README.md
├── .eslintrc.yml
├── package.json
└── CONTRIBUTING.md
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-0", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .DS_Store
4 | dist
5 |
--------------------------------------------------------------------------------
/test/run.sh:
--------------------------------------------------------------------------------
1 | ./node_modules/.bin/mocha --recursive -r test/bootstrap.js "$@"
2 |
--------------------------------------------------------------------------------
/SUPASUPACHATTY/ngrok:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Git-Luv/GitLuv/HEAD/SUPASUPACHATTY/ngrok
--------------------------------------------------------------------------------
/SUPASUPACHATTY/ngrok.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Git-Luv/GitLuv/HEAD/SUPASUPACHATTY/ngrok.zip
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "browser": true,
4 | "esnext": true,
5 | "newcap": false
6 | }
7 |
--------------------------------------------------------------------------------
/client/actions.js:
--------------------------------------------------------------------------------
1 | export const UPDATE_TEXT = 'UPDATE_TEXT';
2 | export const CLEAR_TEXT = 'CLEAR_TEXT';
3 |
--------------------------------------------------------------------------------
/client/public/images/badge.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Git-Luv/GitLuv/HEAD/client/public/images/badge.jpeg
--------------------------------------------------------------------------------
/client/public/images/github.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Git-Luv/GitLuv/HEAD/client/public/images/github.jpeg
--------------------------------------------------------------------------------
/client/public/images/visionary_badge.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Git-Luv/GitLuv/HEAD/client/public/images/visionary_badge.jpeg
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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;
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | GitLuv
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 | })
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | })
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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 | }
--------------------------------------------------------------------------------
/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 | })
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
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 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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/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/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/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/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 |
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 |
180 |
181 | )
182 | }
183 | }
184 |
185 | render() {
186 | return (
187 |
188 |
189 |
190 | { this.returnStage(this.state.stage) }
191 |
192 |
193 |
194 | )
195 | }
196 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 + '...');
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------