├── 10 ├── .DS_Store ├── actions │ └── actionCreators.js ├── components │ ├── Main.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reducers │ ├── comments.js │ ├── index.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 13 ├── .DS_Store ├── actions │ └── actionCreators.js ├── components │ ├── App.js │ ├── Comments.js │ ├── Main.js │ ├── Photo.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reducers │ ├── comments.js │ ├── index.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 14 ├── .DS_Store ├── actions │ └── actionCreators.js ├── components │ ├── App.js │ ├── Comments.js │ ├── Main.js │ ├── Photo.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reducers │ ├── comments.js │ ├── index.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 15 ├── .DS_Store ├── actions │ └── actionCreators.js ├── components │ ├── App.js │ ├── Comments.js │ ├── Main.js │ ├── Photo.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reducers │ ├── comments.js │ ├── index.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 16 ├── .DS_Store ├── actions │ └── actionCreators.js ├── components │ ├── App.js │ ├── Comments.js │ ├── Main.js │ ├── Photo.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reducers │ ├── comments.js │ ├── index.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 17 ├── .DS_Store ├── actions │ └── actionCreators.js ├── components │ ├── App.js │ ├── Comments.js │ ├── Main.js │ ├── Photo.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reducers │ ├── comments.js │ ├── index.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 18 ├── .DS_Store ├── actions │ └── actionCreators.js ├── components │ ├── App.js │ ├── Comments.js │ ├── Main.js │ ├── Photo.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reducers │ ├── comments.js │ ├── index.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 19 ├── actions │ └── actionCreators.js ├── components │ ├── App.js │ ├── Comments.js │ ├── Main.js │ ├── Photo.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reducers │ ├── comments.js │ ├── index.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── .DS_Store ├── .gitignore ├── 05 ├── .DS_Store ├── components │ ├── Main.js │ ├── PhotoGrid.js │ └── Single.js ├── reduxstagram.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 06 ├── .DS_Store ├── components │ ├── Main.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 07 ├── .DS_Store ├── actions │ └── actionCreators.js ├── components │ ├── Main.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── 08 ├── .DS_Store ├── actions │ └── actionCreators.js ├── components │ ├── Main.js │ ├── PhotoGrid.js │ └── Single.js ├── data │ ├── comments.js │ ├── config.js │ └── posts.js ├── reducers │ ├── comments.js │ ├── index.js │ └── posts.js ├── reduxstagram.js ├── store.js └── styles │ ├── _animations.styl │ ├── _normalize.styl │ ├── _typography.styl │ └── style.styl ├── learn-redux ├── .DS_Store ├── .babelrc ├── .eslintrc ├── client │ ├── .DS_Store │ ├── data │ │ ├── comments.js │ │ ├── config.js │ │ └── posts.js │ ├── reduxstagram.js │ └── styles │ │ ├── _animations.styl │ │ ├── _normalize.styl │ │ ├── _typography.styl │ │ └── style.styl ├── devServer.js ├── index.html ├── package.json ├── readme.md ├── webpack.config.dev.js └── webpack.config.prod.js └── readme.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /05/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/05/.DS_Store -------------------------------------------------------------------------------- /05/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /05/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const PhotoGrid = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the photo grid 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default PhotoGrid; 14 | -------------------------------------------------------------------------------- /05/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Single = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the single 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default Single; 14 | -------------------------------------------------------------------------------- /05/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import Main from './components/Main'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | 16 | const router = ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | render(router, document.getElementById('root')); 26 | -------------------------------------------------------------------------------- /05/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /05/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /05/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /05/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /06/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/06/.DS_Store -------------------------------------------------------------------------------- /06/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /06/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const PhotoGrid = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the photo grid 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default PhotoGrid; 14 | -------------------------------------------------------------------------------- /06/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Single = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the single 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default Single; 14 | -------------------------------------------------------------------------------- /06/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /06/data/posts.js: -------------------------------------------------------------------------------- 1 | const posts = [ 2 | { 3 | "code": "BAcyDyQwcXX", 4 | "caption": "Lunch #hamont", 5 | "likes": 56, 6 | "id": "1161022966406956503", 7 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 8 | }, 9 | { 10 | "code": "BAcJeJrQca9", 11 | "caption": "Snow! ⛄️🌨❄️ #lifewithsnickers", 12 | "likes": 59, 13 | "id": "1160844458347054781", 14 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 15 | }, 16 | { 17 | "code": "BAF_KY4wcRY", 18 | "caption": "Cleaned my office and mounted my recording gear overhead. Stoked for 2016!", 19 | "likes": 79, 20 | "id": "1154606670337393752", 21 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 22 | }, 23 | { 24 | "code": "BAPIPRjQce9", 25 | "caption": "Making baby pancakes for one early rising baby. ☕️🍴", 26 | "likes": 47, 27 | "id": "1157179863266871229", 28 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 29 | }, 30 | { 31 | "code": "-hZh6IQcfN", 32 | "caption": "New Stickers just came in. I'll do another mailing in a few weeks if you want some. #javascript", 33 | "likes": 66, 34 | "id": "1126293663140399053", 35 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 36 | }, 37 | { 38 | "code": "-B3eiIwcYV", 39 | "caption": "Tacos for breakfast. I love you Austin. 🇺🇸", 40 | "likes": 33, 41 | "id": "1117418173361145365", 42 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 43 | }, 44 | { 45 | "code": "BAhvZrRwcfu", 46 | "caption": "Tried poke for the first time at @pokehbar. Delicious! It's like a bowl of sushi", 47 | "likes": 30, 48 | "id": "1162418651480049646", 49 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 50 | }, 51 | { 52 | "code": "BAAJqbOQcW5", 53 | "caption": "Brunchin'", 54 | "likes": 40, 55 | "id": "1152964002473690553", 56 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 57 | }, 58 | { 59 | "code": "_4jHytwcUA", 60 | "caption": "2015 can be summed up with one baby and a many lines of code. And sometimes a coding baby. 👶🏼⌨", 61 | "likes": 62, 62 | "id": "1150824171912152320", 63 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 64 | }, 65 | { 66 | "code": "_zbaOlQcbn", 67 | "caption": "Lekker Chocoladeletter", 68 | "likes": 52, 69 | "id": "1149382879529256679", 70 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 71 | }, 72 | { 73 | "code": "_rmvQfQce8", 74 | "caption": "Just discovered the #hamont farmers market has a new ramen place! 🍜", 75 | "likes": 35, 76 | "id": "1147180903383025596", 77 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 78 | }, 79 | { 80 | "code": "_ep9kiQcVy", 81 | "caption": "⛄️", 82 | "likes": 64, 83 | "id": "1143535906423162226", 84 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 85 | }, 86 | { 87 | "code": "_XpJcrwcSn", 88 | "caption": "6 page spread on flexbox in this months netmag!", 89 | "likes": 74, 90 | "id": "1141561999742846119", 91 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 92 | }, 93 | { 94 | "code": "_KnU7MwceA", 95 | "caption": "Hanging out in my office waiting for 5:00 beers to come around.", 96 | "likes": 54, 97 | "id": "1137894817632733056", 98 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 99 | }, 100 | { 101 | "code": "_HMejJQcY5", 102 | "caption": "Today I learned that a long pull espresso is called a 'lungo'", 103 | "likes": 18, 104 | "id": "1136932306813044281", 105 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 106 | }, 107 | { 108 | "code": "_Fq2zmwcaz", 109 | "caption": "Awesome hand lettered gift from @eunibae and the HackerYou crew.", 110 | "likes": 48, 111 | "id": "1136502965197194931", 112 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 113 | }, 114 | { 115 | "code": "_A2r0aQcfD", 116 | "caption": "Some serious hardware meet JavaScript hacks going down this week at hackeryou. Excited for demo day!", 117 | "likes": 57, 118 | "id": "1135147611821557699", 119 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 120 | }, 121 | { 122 | "code": "-1rhFawccs", 123 | "caption": "Some major audio upgrades coming to my next videos 😍", 124 | "likes": 39, 125 | "id": "1132002270913873708", 126 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 127 | }, 128 | { 129 | "code": "-pjx-gQcVi", 130 | "caption": "My baby and me. Thanks to @bearandsparrow for this one.", 131 | "likes": 81, 132 | "id": "1128590547628442978", 133 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 134 | }, 135 | { 136 | "code": "-oTZ0zQcWt", 137 | "caption": "It's too early. Send coffee.", 138 | "likes": 81, 139 | "id": "1128237044221461933", 140 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 141 | }, 142 | { 143 | "code": "-mxKQoQcQh", 144 | "caption": "They both have figured it out. #lifewithsnickers", 145 | "likes": 47, 146 | "id": "1127804966031967265", 147 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 148 | }, 149 | { 150 | "code": "-fasqlQceO", 151 | "caption": "Kaitlin decorated the house for the Christmas. So gezellig! #casabos", 152 | "likes": 46, 153 | "id": "1125735850454402958", 154 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 155 | }, 156 | { 157 | "code": "-VBgtGQcSf", 158 | "caption": "Trying the new Hamilton Brewery beer. Big fan.", 159 | "likes": 27, 160 | "id": "1122810327591928991", 161 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 162 | }, 163 | { 164 | "code": "-FpTyHQcau", 165 | "caption": "I'm in Austin for a conference and doing some training. Enjoying some local brew with my baby.", 166 | "likes": 82, 167 | "id": "1118481761857291950", 168 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 169 | } 170 | ]; 171 | 172 | 173 | export default posts; 174 | -------------------------------------------------------------------------------- /06/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import Main from './components/Main'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | 16 | const router = ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | render(router, document.getElementById('root')); 26 | -------------------------------------------------------------------------------- /06/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compse } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /06/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /06/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /06/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /06/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /07/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/07/.DS_Store -------------------------------------------------------------------------------- /07/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /07/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /07/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const PhotoGrid = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the photo grid 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default PhotoGrid; 14 | -------------------------------------------------------------------------------- /07/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Single = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the single 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default Single; 14 | -------------------------------------------------------------------------------- /07/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /07/data/posts.js: -------------------------------------------------------------------------------- 1 | const posts = [ 2 | { 3 | "code": "BAcyDyQwcXX", 4 | "caption": "Lunch #hamont", 5 | "likes": 56, 6 | "id": "1161022966406956503", 7 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 8 | }, 9 | { 10 | "code": "BAcJeJrQca9", 11 | "caption": "Snow! ⛄️🌨❄️ #lifewithsnickers", 12 | "likes": 59, 13 | "id": "1160844458347054781", 14 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 15 | }, 16 | { 17 | "code": "BAF_KY4wcRY", 18 | "caption": "Cleaned my office and mounted my recording gear overhead. Stoked for 2016!", 19 | "likes": 79, 20 | "id": "1154606670337393752", 21 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 22 | }, 23 | { 24 | "code": "BAPIPRjQce9", 25 | "caption": "Making baby pancakes for one early rising baby. ☕️🍴", 26 | "likes": 47, 27 | "id": "1157179863266871229", 28 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 29 | }, 30 | { 31 | "code": "-hZh6IQcfN", 32 | "caption": "New Stickers just came in. I'll do another mailing in a few weeks if you want some. #javascript", 33 | "likes": 66, 34 | "id": "1126293663140399053", 35 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 36 | }, 37 | { 38 | "code": "-B3eiIwcYV", 39 | "caption": "Tacos for breakfast. I love you Austin. 🇺🇸", 40 | "likes": 33, 41 | "id": "1117418173361145365", 42 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 43 | }, 44 | { 45 | "code": "BAhvZrRwcfu", 46 | "caption": "Tried poke for the first time at @pokehbar. Delicious! It's like a bowl of sushi", 47 | "likes": 30, 48 | "id": "1162418651480049646", 49 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 50 | }, 51 | { 52 | "code": "BAAJqbOQcW5", 53 | "caption": "Brunchin'", 54 | "likes": 40, 55 | "id": "1152964002473690553", 56 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 57 | }, 58 | { 59 | "code": "_4jHytwcUA", 60 | "caption": "2015 can be summed up with one baby and a many lines of code. And sometimes a coding baby. 👶🏼⌨", 61 | "likes": 62, 62 | "id": "1150824171912152320", 63 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 64 | }, 65 | { 66 | "code": "_zbaOlQcbn", 67 | "caption": "Lekker Chocoladeletter", 68 | "likes": 52, 69 | "id": "1149382879529256679", 70 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 71 | }, 72 | { 73 | "code": "_rmvQfQce8", 74 | "caption": "Just discovered the #hamont farmers market has a new ramen place! 🍜", 75 | "likes": 35, 76 | "id": "1147180903383025596", 77 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 78 | }, 79 | { 80 | "code": "_ep9kiQcVy", 81 | "caption": "⛄️", 82 | "likes": 64, 83 | "id": "1143535906423162226", 84 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 85 | }, 86 | { 87 | "code": "_XpJcrwcSn", 88 | "caption": "6 page spread on flexbox in this months netmag!", 89 | "likes": 74, 90 | "id": "1141561999742846119", 91 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 92 | }, 93 | { 94 | "code": "_KnU7MwceA", 95 | "caption": "Hanging out in my office waiting for 5:00 beers to come around.", 96 | "likes": 54, 97 | "id": "1137894817632733056", 98 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 99 | }, 100 | { 101 | "code": "_HMejJQcY5", 102 | "caption": "Today I learned that a long pull espresso is called a 'lungo'", 103 | "likes": 18, 104 | "id": "1136932306813044281", 105 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 106 | }, 107 | { 108 | "code": "_Fq2zmwcaz", 109 | "caption": "Awesome hand lettered gift from @eunibae and the HackerYou crew.", 110 | "likes": 48, 111 | "id": "1136502965197194931", 112 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 113 | }, 114 | { 115 | "code": "_A2r0aQcfD", 116 | "caption": "Some serious hardware meet JavaScript hacks going down this week at hackeryou. Excited for demo day!", 117 | "likes": 57, 118 | "id": "1135147611821557699", 119 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 120 | }, 121 | { 122 | "code": "-1rhFawccs", 123 | "caption": "Some major audio upgrades coming to my next videos 😍", 124 | "likes": 39, 125 | "id": "1132002270913873708", 126 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 127 | }, 128 | { 129 | "code": "-pjx-gQcVi", 130 | "caption": "My baby and me. Thanks to @bearandsparrow for this one.", 131 | "likes": 81, 132 | "id": "1128590547628442978", 133 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 134 | }, 135 | { 136 | "code": "-oTZ0zQcWt", 137 | "caption": "It's too early. Send coffee.", 138 | "likes": 81, 139 | "id": "1128237044221461933", 140 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 141 | }, 142 | { 143 | "code": "-mxKQoQcQh", 144 | "caption": "They both have figured it out. #lifewithsnickers", 145 | "likes": 47, 146 | "id": "1127804966031967265", 147 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 148 | }, 149 | { 150 | "code": "-fasqlQceO", 151 | "caption": "Kaitlin decorated the house for the Christmas. So gezellig! #casabos", 152 | "likes": 46, 153 | "id": "1125735850454402958", 154 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 155 | }, 156 | { 157 | "code": "-VBgtGQcSf", 158 | "caption": "Trying the new Hamilton Brewery beer. Big fan.", 159 | "likes": 27, 160 | "id": "1122810327591928991", 161 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 162 | }, 163 | { 164 | "code": "-FpTyHQcau", 165 | "caption": "I'm in Austin for a conference and doing some training. Enjoying some local brew with my baby.", 166 | "likes": 82, 167 | "id": "1118481761857291950", 168 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 169 | } 170 | ]; 171 | 172 | 173 | export default posts; 174 | -------------------------------------------------------------------------------- /07/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import Main from './components/Main'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | 16 | const router = ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | render(router, document.getElementById('root')); 26 | -------------------------------------------------------------------------------- /07/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compse } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /07/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /07/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /07/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /07/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /08/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/08/.DS_Store -------------------------------------------------------------------------------- /08/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /08/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /08/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const PhotoGrid = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the photo grid 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default PhotoGrid; 14 | -------------------------------------------------------------------------------- /08/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Single = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the single 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default Single; 14 | -------------------------------------------------------------------------------- /08/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /08/data/posts.js: -------------------------------------------------------------------------------- 1 | const posts = [ 2 | { 3 | "code": "BAcyDyQwcXX", 4 | "caption": "Lunch #hamont", 5 | "likes": 56, 6 | "id": "1161022966406956503", 7 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 8 | }, 9 | { 10 | "code": "BAcJeJrQca9", 11 | "caption": "Snow! ⛄️🌨❄️ #lifewithsnickers", 12 | "likes": 59, 13 | "id": "1160844458347054781", 14 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 15 | }, 16 | { 17 | "code": "BAF_KY4wcRY", 18 | "caption": "Cleaned my office and mounted my recording gear overhead. Stoked for 2016!", 19 | "likes": 79, 20 | "id": "1154606670337393752", 21 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 22 | }, 23 | { 24 | "code": "BAPIPRjQce9", 25 | "caption": "Making baby pancakes for one early rising baby. ☕️🍴", 26 | "likes": 47, 27 | "id": "1157179863266871229", 28 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 29 | }, 30 | { 31 | "code": "-hZh6IQcfN", 32 | "caption": "New Stickers just came in. I'll do another mailing in a few weeks if you want some. #javascript", 33 | "likes": 66, 34 | "id": "1126293663140399053", 35 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 36 | }, 37 | { 38 | "code": "-B3eiIwcYV", 39 | "caption": "Tacos for breakfast. I love you Austin. 🇺🇸", 40 | "likes": 33, 41 | "id": "1117418173361145365", 42 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 43 | }, 44 | { 45 | "code": "BAhvZrRwcfu", 46 | "caption": "Tried poke for the first time at @pokehbar. Delicious! It's like a bowl of sushi", 47 | "likes": 30, 48 | "id": "1162418651480049646", 49 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 50 | }, 51 | { 52 | "code": "BAAJqbOQcW5", 53 | "caption": "Brunchin'", 54 | "likes": 40, 55 | "id": "1152964002473690553", 56 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 57 | }, 58 | { 59 | "code": "_4jHytwcUA", 60 | "caption": "2015 can be summed up with one baby and a many lines of code. And sometimes a coding baby. 👶🏼⌨", 61 | "likes": 62, 62 | "id": "1150824171912152320", 63 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 64 | }, 65 | { 66 | "code": "_zbaOlQcbn", 67 | "caption": "Lekker Chocoladeletter", 68 | "likes": 52, 69 | "id": "1149382879529256679", 70 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 71 | }, 72 | { 73 | "code": "_rmvQfQce8", 74 | "caption": "Just discovered the #hamont farmers market has a new ramen place! 🍜", 75 | "likes": 35, 76 | "id": "1147180903383025596", 77 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 78 | }, 79 | { 80 | "code": "_ep9kiQcVy", 81 | "caption": "⛄️", 82 | "likes": 64, 83 | "id": "1143535906423162226", 84 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 85 | }, 86 | { 87 | "code": "_XpJcrwcSn", 88 | "caption": "6 page spread on flexbox in this months netmag!", 89 | "likes": 74, 90 | "id": "1141561999742846119", 91 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 92 | }, 93 | { 94 | "code": "_KnU7MwceA", 95 | "caption": "Hanging out in my office waiting for 5:00 beers to come around.", 96 | "likes": 54, 97 | "id": "1137894817632733056", 98 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 99 | }, 100 | { 101 | "code": "_HMejJQcY5", 102 | "caption": "Today I learned that a long pull espresso is called a 'lungo'", 103 | "likes": 18, 104 | "id": "1136932306813044281", 105 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 106 | }, 107 | { 108 | "code": "_Fq2zmwcaz", 109 | "caption": "Awesome hand lettered gift from @eunibae and the HackerYou crew.", 110 | "likes": 48, 111 | "id": "1136502965197194931", 112 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 113 | }, 114 | { 115 | "code": "_A2r0aQcfD", 116 | "caption": "Some serious hardware meet JavaScript hacks going down this week at hackeryou. Excited for demo day!", 117 | "likes": 57, 118 | "id": "1135147611821557699", 119 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 120 | }, 121 | { 122 | "code": "-1rhFawccs", 123 | "caption": "Some major audio upgrades coming to my next videos 😍", 124 | "likes": 39, 125 | "id": "1132002270913873708", 126 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 127 | }, 128 | { 129 | "code": "-pjx-gQcVi", 130 | "caption": "My baby and me. Thanks to @bearandsparrow for this one.", 131 | "likes": 81, 132 | "id": "1128590547628442978", 133 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 134 | }, 135 | { 136 | "code": "-oTZ0zQcWt", 137 | "caption": "It's too early. Send coffee.", 138 | "likes": 81, 139 | "id": "1128237044221461933", 140 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 141 | }, 142 | { 143 | "code": "-mxKQoQcQh", 144 | "caption": "They both have figured it out. #lifewithsnickers", 145 | "likes": 47, 146 | "id": "1127804966031967265", 147 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 148 | }, 149 | { 150 | "code": "-fasqlQceO", 151 | "caption": "Kaitlin decorated the house for the Christmas. So gezellig! #casabos", 152 | "likes": 46, 153 | "id": "1125735850454402958", 154 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 155 | }, 156 | { 157 | "code": "-VBgtGQcSf", 158 | "caption": "Trying the new Hamilton Brewery beer. Big fan.", 159 | "likes": 27, 160 | "id": "1122810327591928991", 161 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 162 | }, 163 | { 164 | "code": "-FpTyHQcau", 165 | "caption": "I'm in Austin for a conference and doing some training. Enjoying some local brew with my baby.", 166 | "likes": 82, 167 | "id": "1118481761857291950", 168 | "display_src": `https://picsum.photos/400/400/?image=${Math.floor((Math.random() * 85))}`, 169 | } 170 | ]; 171 | 172 | 173 | export default posts; 174 | -------------------------------------------------------------------------------- /08/reducers/comments.js: -------------------------------------------------------------------------------- 1 | function comments(state = [], action) { 2 | console.log(state, action); 3 | return state; 4 | } 5 | 6 | export default comments; 7 | -------------------------------------------------------------------------------- /08/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import posts from './posts'; 5 | import comments from './comments'; 6 | 7 | const rootReducer = combineReducers({posts, comments, router: routerReducer }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /08/reducers/posts.js: -------------------------------------------------------------------------------- 1 | // a reducer takes in two things: 2 | 3 | // 1. the action (info about what happened) 4 | // 2. copy of current state 5 | 6 | function posts(state = [], action) { 7 | console.log(state, action); 8 | return state; 9 | } 10 | 11 | export default posts; 12 | -------------------------------------------------------------------------------- /08/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import Main from './components/Main'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | 16 | const router = ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | render(router, document.getElementById('root')); 26 | -------------------------------------------------------------------------------- /08/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compse } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /08/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /08/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /08/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /08/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /10/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/10/.DS_Store -------------------------------------------------------------------------------- /10/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /10/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /10/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const PhotoGrid = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the photo grid 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default PhotoGrid; 14 | -------------------------------------------------------------------------------- /10/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Single = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | I'm the single 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default Single; 14 | -------------------------------------------------------------------------------- /10/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /10/reducers/comments.js: -------------------------------------------------------------------------------- 1 | function comments(state = [], action) { 2 | console.log(state, action); 3 | return state; 4 | } 5 | 6 | export default comments; 7 | -------------------------------------------------------------------------------- /10/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import posts from './posts'; 5 | import comments from './comments'; 6 | 7 | const rootReducer = combineReducers({posts, comments, routing: routerReducer }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /10/reducers/posts.js: -------------------------------------------------------------------------------- 1 | // a reducer takes in two things: 2 | 3 | // 1. the action (info about what happened) 4 | // 2. copy of current state 5 | 6 | function posts(state = [], action) { 7 | console.log("The post will change"); 8 | console.log(state, action); 9 | return state; 10 | } 11 | 12 | export default posts; 13 | -------------------------------------------------------------------------------- /10/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import Main from './components/Main'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | import { Provider } from 'react-redux'; 16 | import store, { history } from './store'; 17 | 18 | const router = ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | 29 | render(router, document.getElementById('root')); 30 | -------------------------------------------------------------------------------- /10/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compse } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /10/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /10/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /10/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /10/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /13/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/13/.DS_Store -------------------------------------------------------------------------------- /13/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /13/components/App.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as actionCreators from '../actions/actionCreators'; 4 | import Main from './Main'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | posts: state.posts, 9 | comments: state.comments 10 | } 11 | } 12 | 13 | function mapDispachToProps(dispatch) { 14 | return bindActionCreators(actionCreators, dispatch); 15 | } 16 | 17 | const App = connect(mapStateToProps, mapDispachToProps)(Main); 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /13/components/Comments.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/13/components/Comments.js -------------------------------------------------------------------------------- /13/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /13/components/Photo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import CSSTransitionGroup from 'react-addons-css-transition-group'; 4 | 5 | const Photo = React.createClass({ 6 | render() { 7 | const { post, i, comments } = this.props; 8 | return ( 9 |
10 |
11 | 12 | {post.caption} 13 | 14 | 15 | 16 | {post.likes} 17 | 18 | 19 |
20 | 21 |
22 |

{post.caption}

23 |
24 | 25 | 26 | 27 | 28 | {comments[post.code] ? comments[post.code].length : 0 } 29 | 30 | 31 |
32 |
33 | 34 |
35 | ) 36 | } 37 | }); 38 | 39 | export default Photo; 40 | -------------------------------------------------------------------------------- /13/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | 4 | const PhotoGrid = React.createClass({ 5 | render() { 6 | return ( 7 |
8 | {this.props.posts.map((post, i) => )} 9 |
10 | ) 11 | } 12 | }); 13 | 14 | export default PhotoGrid; 15 | -------------------------------------------------------------------------------- /13/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Single = React.createClass({ 4 | render() { 5 | return ( 6 |
7 | Im the single 8 |
9 | ) 10 | } 11 | }); 12 | 13 | export default Single; 14 | -------------------------------------------------------------------------------- /13/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /13/reducers/comments.js: -------------------------------------------------------------------------------- 1 | function comments(state = [], action) { 2 | return state; 3 | } 4 | 5 | export default comments; 6 | -------------------------------------------------------------------------------- /13/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import posts from './posts'; 5 | import comments from './comments'; 6 | 7 | const rootReducer = combineReducers({posts, comments, routing: routerReducer }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /13/reducers/posts.js: -------------------------------------------------------------------------------- 1 | // a reducer takes in two things: 2 | 3 | // 1. the action (info about what happened) 4 | // 2. copy of current state 5 | 6 | function posts(state = [], action) { 7 | switch(action.type) { 8 | case 'INCREMENT_LIKES' : 9 | console.log("Incrementing Likes!!"); 10 | const i = action.index; 11 | return [ 12 | ...state.slice(0,i), // before the one we are updating 13 | {...state[i], likes: state[i].likes + 1}, 14 | ...state.slice(i + 1), // after the one we are updating 15 | ] 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export default posts; 22 | -------------------------------------------------------------------------------- /13/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import App from './components/App'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | import { Provider } from 'react-redux'; 16 | import store, { history } from './store'; 17 | 18 | const router = ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | 29 | render(router, document.getElementById('root')); 30 | -------------------------------------------------------------------------------- /13/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compse } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /13/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /13/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /13/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /13/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /14/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/14/.DS_Store -------------------------------------------------------------------------------- /14/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /14/components/App.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as actionCreators from '../actions/actionCreators'; 4 | import Main from './Main'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | posts: state.posts, 9 | comments: state.comments 10 | } 11 | } 12 | 13 | function mapDispachToProps(dispatch) { 14 | return bindActionCreators(actionCreators, dispatch); 15 | } 16 | 17 | const App = connect(mapStateToProps, mapDispachToProps)(Main); 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /14/components/Comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Comments = React.createClass({ 4 | renderComment(comment, i) { 5 | return ( 6 |
7 |

8 | {comment.user} 9 | {comment.text} 10 | 11 |

12 |
13 | ) 14 | }, 15 | 16 | render() { 17 | return ( 18 |
19 | {this.props.postComments.map(this.renderComment)} 20 |
21 | 22 | 23 | 24 |
25 |
26 | ) 27 | } 28 | }); 29 | 30 | export default Comments; 31 | -------------------------------------------------------------------------------- /14/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /14/components/Photo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import CSSTransitionGroup from 'react-addons-css-transition-group'; 4 | 5 | const Photo = React.createClass({ 6 | render() { 7 | const { post, i, comments } = this.props; 8 | return ( 9 |
10 |
11 | 12 | {post.caption} 13 | 14 | 15 | 16 | {post.likes} 17 | 18 | 19 |
20 | 21 |
22 |

{post.caption}

23 |
24 | 25 | 26 | 27 | 28 | {comments[post.code] ? comments[post.code].length : 0 } 29 | 30 | 31 |
32 |
33 | 34 |
35 | ) 36 | } 37 | }); 38 | 39 | export default Photo; 40 | -------------------------------------------------------------------------------- /14/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | 4 | const PhotoGrid = React.createClass({ 5 | render() { 6 | return ( 7 |
8 | {this.props.posts.map((post, i) => )} 9 |
10 | ) 11 | } 12 | }); 13 | 14 | export default PhotoGrid; 15 | -------------------------------------------------------------------------------- /14/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | import Comments from './Comments'; 4 | 5 | const Single = React.createClass({ 6 | render() { 7 | const { postId } = this.props.params; 8 | 9 | const i = this.props.posts.findIndex((post) => post.code === postId); 10 | const post = this.props.posts[i]; 11 | 12 | const postComments = this.props.comments[postId] || []; 13 | 14 | return ( 15 |
16 | 17 | 18 |
19 | ) 20 | } 21 | }); 22 | 23 | export default Single; 24 | -------------------------------------------------------------------------------- /14/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /14/reducers/comments.js: -------------------------------------------------------------------------------- 1 | function comments(state = [], action) { 2 | return state; 3 | } 4 | 5 | export default comments; 6 | -------------------------------------------------------------------------------- /14/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import posts from './posts'; 5 | import comments from './comments'; 6 | 7 | const rootReducer = combineReducers({posts, comments, routing: routerReducer }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /14/reducers/posts.js: -------------------------------------------------------------------------------- 1 | // a reducer takes in two things: 2 | 3 | // 1. the action (info about what happened) 4 | // 2. copy of current state 5 | 6 | function posts(state = [], action) { 7 | switch(action.type) { 8 | case 'INCREMENT_LIKES' : 9 | console.log("Incrementing Likes!!"); 10 | const i = action.index; 11 | return [ 12 | ...state.slice(0,i), // before the one we are updating 13 | {...state[i], likes: state[i].likes + 1}, 14 | ...state.slice(i + 1), // after the one we are updating 15 | ] 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export default posts; 22 | -------------------------------------------------------------------------------- /14/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import App from './components/App'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | import { Provider } from 'react-redux'; 16 | import store, { history } from './store'; 17 | 18 | const router = ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | 29 | render(router, document.getElementById('root')); 30 | -------------------------------------------------------------------------------- /14/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compse } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /14/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /14/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /14/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /14/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /15/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/15/.DS_Store -------------------------------------------------------------------------------- /15/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /15/components/App.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as actionCreators from '../actions/actionCreators'; 4 | import Main from './Main'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | posts: state.posts, 9 | comments: state.comments 10 | } 11 | } 12 | 13 | function mapDispachToProps(dispatch) { 14 | return bindActionCreators(actionCreators, dispatch); 15 | } 16 | 17 | const App = connect(mapStateToProps, mapDispachToProps)(Main); 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /15/components/Comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Comments = React.createClass({ 4 | renderComment(comment, i) { 5 | return ( 6 |
7 |

8 | {comment.user} 9 | {comment.text} 10 | 11 |

12 |
13 | ) 14 | }, 15 | render() { 16 | return ( 17 |
18 | {this.props.postComments.map(this.renderComment)} 19 |
20 | 21 | 22 | 23 |
24 |
25 | ) 26 | } 27 | }); 28 | 29 | export default Comments; 30 | -------------------------------------------------------------------------------- /15/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /15/components/Photo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import CSSTransitionGroup from 'react-addons-css-transition-group'; 4 | 5 | const Photo = React.createClass({ 6 | render() { 7 | const { post, i, comments } = this.props; 8 | return ( 9 |
10 |
11 | 12 | {post.caption} 13 | 14 | 15 | 16 | {post.likes} 17 | 18 | 19 |
20 | 21 |
22 |

{post.caption}

23 |
24 | 25 | 26 | 27 | 28 | {comments[post.code] ? comments[post.code].length : 0 } 29 | 30 | 31 |
32 |
33 | 34 |
35 | ) 36 | } 37 | }); 38 | 39 | export default Photo; 40 | -------------------------------------------------------------------------------- /15/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | 4 | const PhotoGrid = React.createClass({ 5 | render() { 6 | return ( 7 |
8 | {this.props.posts.map((post, i) => )} 9 |
10 | ) 11 | } 12 | }); 13 | 14 | export default PhotoGrid; 15 | -------------------------------------------------------------------------------- /15/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | import Comments from './Comments'; 4 | 5 | const Single = React.createClass({ 6 | render() { 7 | const { postId } = this.props.params; 8 | 9 | const i = this.props.posts.findIndex((post) => post.code === postId); 10 | const post = this.props.posts[i]; 11 | 12 | const postComments = this.props.comments[postId] || []; 13 | 14 | return ( 15 |
16 | 17 | 18 |
19 | ) 20 | } 21 | }); 22 | 23 | export default Single; 24 | -------------------------------------------------------------------------------- /15/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /15/reducers/comments.js: -------------------------------------------------------------------------------- 1 | function comments(state = [], action) { 2 | return state; 3 | } 4 | 5 | export default comments; 6 | -------------------------------------------------------------------------------- /15/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import posts from './posts'; 5 | import comments from './comments'; 6 | 7 | const rootReducer = combineReducers({posts, comments, routing: routerReducer }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /15/reducers/posts.js: -------------------------------------------------------------------------------- 1 | // a reducer takes in two things: 2 | 3 | // 1. the action (info about what happened) 4 | // 2. copy of current state 5 | 6 | function posts(state = [], action) { 7 | switch(action.type) { 8 | case 'INCREMENT_LIKES' : 9 | console.log("Incrementing Likes!!"); 10 | const i = action.index; 11 | return [ 12 | ...state.slice(0,i), // before the one we are updating 13 | {...state[i], likes: state[i].likes + 1}, 14 | ...state.slice(i + 1), // after the one we are updating 15 | ] 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export default posts; 22 | -------------------------------------------------------------------------------- /15/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import App from './components/App'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | import { Provider } from 'react-redux'; 16 | import store, { history } from './store'; 17 | 18 | import Raven from 'raven-js'; 19 | import { sentry_url, logException } from './data/config'; 20 | 21 | Raven.config(sentry_url, { 22 | tags: { 23 | git_commit: 'as09d8f09' 24 | } 25 | }).install(); 26 | 27 | logException(new Error('Incomplete Data!'), { 28 | email: 'wesbos@gmail.com' 29 | }); 30 | 31 | Raven.showReportDialog(); 32 | 33 | const router = ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ) 43 | 44 | render(router, document.getElementById('root')); 45 | -------------------------------------------------------------------------------- /15/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compse } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /15/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /15/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /15/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /15/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /16/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/16/.DS_Store -------------------------------------------------------------------------------- /16/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /16/components/App.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as actionCreators from '../actions/actionCreators'; 4 | import Main from './Main'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | posts: state.posts, 9 | comments: state.comments 10 | } 11 | } 12 | 13 | function mapDispachToProps(dispatch) { 14 | return bindActionCreators(actionCreators, dispatch); 15 | } 16 | 17 | const App = connect(mapStateToProps, mapDispachToProps)(Main); 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /16/components/Comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Comments = React.createClass({ 4 | renderComment(comment, i) { 5 | return ( 6 |
7 |

8 | {comment.user} 9 | {comment.text} 10 | 11 |

12 |
13 | ) 14 | }, 15 | handleSubmit(e) { 16 | e.preventDefault(); 17 | const { postId } = this.props.params; 18 | const author = this.refs.author.value; 19 | const comment = this.refs.comment.value; 20 | this.props.addComment(postId, author, comment); 21 | this.refs.commentForm.reset(); 22 | }, 23 | render() { 24 | return ( 25 |
26 | {this.props.postComments.map(this.renderComment)} 27 |
28 | 29 | 30 | 31 |
32 |
33 | ) 34 | } 35 | }); 36 | 37 | export default Comments; 38 | -------------------------------------------------------------------------------- /16/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /16/components/Photo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import CSSTransitionGroup from 'react-addons-css-transition-group'; 4 | 5 | const Photo = React.createClass({ 6 | render() { 7 | const { post, i, comments } = this.props; 8 | return ( 9 |
10 |
11 | 12 | {post.caption} 13 | 14 | 15 | 16 | {post.likes} 17 | 18 | 19 |
20 | 21 |
22 |

{post.caption}

23 |
24 | 25 | 26 | 27 | 28 | {comments[post.code] ? comments[post.code].length : 0 } 29 | 30 | 31 |
32 |
33 | 34 |
35 | ) 36 | } 37 | }); 38 | 39 | export default Photo; 40 | -------------------------------------------------------------------------------- /16/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | 4 | const PhotoGrid = React.createClass({ 5 | render() { 6 | return ( 7 |
8 | {this.props.posts.map((post, i) => )} 9 |
10 | ) 11 | } 12 | }); 13 | 14 | export default PhotoGrid; 15 | -------------------------------------------------------------------------------- /16/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | import Comments from './Comments'; 4 | 5 | const Single = React.createClass({ 6 | render() { 7 | const { postId } = this.props.params; 8 | 9 | const i = this.props.posts.findIndex((post) => post.code === postId); 10 | const post = this.props.posts[i]; 11 | 12 | const postComments = this.props.comments[postId] || []; 13 | 14 | return ( 15 |
16 | 17 | 18 |
19 | ) 20 | } 21 | }); 22 | 23 | export default Single; 24 | -------------------------------------------------------------------------------- /16/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /16/reducers/comments.js: -------------------------------------------------------------------------------- 1 | function comments(state = [], action) { 2 | return state; 3 | } 4 | 5 | export default comments; 6 | -------------------------------------------------------------------------------- /16/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import posts from './posts'; 5 | import comments from './comments'; 6 | 7 | const rootReducer = combineReducers({posts, comments, routing: routerReducer }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /16/reducers/posts.js: -------------------------------------------------------------------------------- 1 | // a reducer takes in two things: 2 | 3 | // 1. the action (info about what happened) 4 | // 2. copy of current state 5 | 6 | function posts(state = [], action) { 7 | switch(action.type) { 8 | case 'INCREMENT_LIKES' : 9 | console.log("Incrementing Likes!!"); 10 | const i = action.index; 11 | return [ 12 | ...state.slice(0,i), // before the one we are updating 13 | {...state[i], likes: state[i].likes + 1}, 14 | ...state.slice(i + 1), // after the one we are updating 15 | ] 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export default posts; 22 | -------------------------------------------------------------------------------- /16/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import App from './components/App'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | import { Provider } from 'react-redux'; 16 | import store, { history } from './store'; 17 | 18 | import Raven from 'raven-js'; 19 | import { sentry_url, logException } from './data/config'; 20 | 21 | Raven.config(sentry_url, { 22 | tags: { 23 | git_commit: 'as09d8f09' 24 | } 25 | }).install(); 26 | 27 | logException(new Error('Incomplete Data!'), { 28 | email: 'wesbos@gmail.com' 29 | }); 30 | 31 | Raven.showReportDialog(); 32 | 33 | const router = ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ) 43 | 44 | render(router, document.getElementById('root')); 45 | -------------------------------------------------------------------------------- /16/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compse } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /16/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /16/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /16/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /16/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /17/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/17/.DS_Store -------------------------------------------------------------------------------- /17/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /17/components/App.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as actionCreators from '../actions/actionCreators'; 4 | import Main from './Main'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | posts: state.posts, 9 | comments: state.comments 10 | } 11 | } 12 | 13 | function mapDispachToProps(dispatch) { 14 | return bindActionCreators(actionCreators, dispatch); 15 | } 16 | 17 | const App = connect(mapStateToProps, mapDispachToProps)(Main); 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /17/components/Comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Comments = React.createClass({ 4 | renderComment(comment, i) { 5 | return ( 6 |
7 |

8 | {comment.user} 9 | {comment.text} 10 | 11 |

12 |
13 | ) 14 | }, 15 | handleSubmit(e) { 16 | e.preventDefault(); 17 | const { postId } = this.props.params; 18 | const author = this.refs.author.value; 19 | const comment = this.refs.comment.value; 20 | this.props.addComment(postId, author, comment); 21 | this.refs.commentForm.reset(); 22 | }, 23 | render() { 24 | return ( 25 |
26 | {this.props.postComments.map(this.renderComment)} 27 |
28 | 29 | 30 | 31 |
32 |
33 | ) 34 | } 35 | }); 36 | 37 | export default Comments; 38 | -------------------------------------------------------------------------------- /17/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /17/components/Photo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import CSSTransitionGroup from 'react-addons-css-transition-group'; 4 | 5 | const Photo = React.createClass({ 6 | render() { 7 | const { post, i, comments } = this.props; 8 | return ( 9 |
10 |
11 | 12 | {post.caption} 13 | 14 | 15 | 16 | {post.likes} 17 | 18 | 19 |
20 | 21 |
22 |

{post.caption}

23 |
24 | 25 | 26 | 27 | 28 | {comments[post.code] ? comments[post.code].length : 0 } 29 | 30 | 31 |
32 |
33 | 34 |
35 | ) 36 | } 37 | }); 38 | 39 | export default Photo; 40 | -------------------------------------------------------------------------------- /17/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | 4 | const PhotoGrid = React.createClass({ 5 | render() { 6 | return ( 7 |
8 | {this.props.posts.map((post, i) => )} 9 |
10 | ) 11 | } 12 | }); 13 | 14 | export default PhotoGrid; 15 | -------------------------------------------------------------------------------- /17/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | import Comments from './Comments'; 4 | 5 | const Single = React.createClass({ 6 | render() { 7 | const { postId } = this.props.params; 8 | 9 | const i = this.props.posts.findIndex((post) => post.code === postId); 10 | const post = this.props.posts[i]; 11 | 12 | const postComments = this.props.comments[postId] || []; 13 | 14 | return ( 15 |
16 | 17 | 18 |
19 | ) 20 | } 21 | }); 22 | 23 | export default Single; 24 | -------------------------------------------------------------------------------- /17/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /17/reducers/comments.js: -------------------------------------------------------------------------------- 1 | function postComments(state = [], action) { 2 | switch(action.type){ 3 | case 'ADD_COMMENT': 4 | // return the new state with the new comment 5 | return [...state,{ 6 | user: action.author, 7 | text: action.comment 8 | }]; 9 | case 'REMOVE_COMMENT': 10 | // we need to return the new state without the deleted comment 11 | return [ 12 | // from the start to the one we want to delete 13 | ...state.slice(0,action.i), 14 | // after the deleted one, to the end 15 | ...state.slice(action.i + 1) 16 | ] 17 | default: 18 | return state; 19 | } 20 | return state; 21 | } 22 | 23 | function comments(state = [], action) { 24 | if(typeof action.postId !== 'undefined') { 25 | return { 26 | // take the current state 27 | ...state, 28 | // overwrite this post with a new one 29 | [action.postId]: postComments(state[action.postId], action) 30 | } 31 | } 32 | return state; 33 | } 34 | 35 | export default comments; 36 | -------------------------------------------------------------------------------- /17/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import posts from './posts'; 5 | import comments from './comments'; 6 | 7 | const rootReducer = combineReducers({posts, comments, routing: routerReducer }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /17/reducers/posts.js: -------------------------------------------------------------------------------- 1 | // a reducer takes in two things: 2 | 3 | // 1. the action (info about what happened) 4 | // 2. copy of current state 5 | 6 | function posts(state = [], action) { 7 | switch(action.type) { 8 | case 'INCREMENT_LIKES' : 9 | console.log("Incrementing Likes!!"); 10 | const i = action.index; 11 | return [ 12 | ...state.slice(0,i), // before the one we are updating 13 | {...state[i], likes: state[i].likes + 1}, 14 | ...state.slice(i + 1), // after the one we are updating 15 | ] 16 | default: 17 | return state; 18 | } 19 | } 20 | 21 | export default posts; 22 | -------------------------------------------------------------------------------- /17/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import App from './components/App'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | import { Provider } from 'react-redux'; 16 | import store, { history } from './store'; 17 | 18 | import Raven from 'raven-js'; 19 | import { sentry_url, logException } from './data/config'; 20 | 21 | Raven.config(sentry_url, { 22 | tags: { 23 | git_commit: 'as09d8f09' 24 | } 25 | }).install(); 26 | 27 | logException(new Error('Incomplete Data!'), { 28 | email: 'wesbos@gmail.com' 29 | }); 30 | 31 | Raven.showReportDialog(); 32 | 33 | const router = ( 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ) 43 | 44 | render(router, document.getElementById('root')); 45 | -------------------------------------------------------------------------------- /17/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compse } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /17/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /17/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /17/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /17/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /18/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/18/.DS_Store -------------------------------------------------------------------------------- /18/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /18/components/App.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as actionCreators from '../actions/actionCreators'; 4 | import Main from './Main'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | posts: state.posts, 9 | comments: state.comments 10 | } 11 | } 12 | 13 | function mapDispachToProps(dispatch) { 14 | return bindActionCreators(actionCreators, dispatch); 15 | } 16 | 17 | const App = connect(mapStateToProps, mapDispachToProps)(Main); 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /18/components/Comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Comments = React.createClass({ 4 | renderComment(comment, i) { 5 | return ( 6 |
7 |

8 | {comment.user} 9 | {comment.text} 10 | 11 |

12 |
13 | ) 14 | }, 15 | handleSubmit(e) { 16 | e.preventDefault(); 17 | const { postId } = this.props.params; 18 | const author = this.refs.author.value; 19 | const comment = this.refs.comment.value; 20 | this.props.addComment(postId, author, comment); 21 | this.refs.commentForm.reset(); 22 | }, 23 | render() { 24 | return ( 25 |
26 | {this.props.postComments.map(this.renderComment)} 27 |
28 | 29 | 30 | 31 |
32 |
33 | ) 34 | } 35 | }); 36 | 37 | export default Comments; 38 | -------------------------------------------------------------------------------- /18/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement(this.props.children, this.props)} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /18/components/Photo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import CSSTransitionGroup from 'react-addons-css-transition-group'; 4 | 5 | const Photo = React.createClass({ 6 | render() { 7 | const { post, i, comments } = this.props; 8 | return ( 9 |
10 |
11 | 12 | {post.caption} 13 | 14 | 15 | 16 | {post.likes} 17 | 18 | 19 |
20 | 21 |
22 |

{post.caption}

23 |
24 | 25 | 26 | 27 | 28 | {comments[post.code] ? comments[post.code].length : 0 } 29 | 30 | 31 |
32 |
33 | 34 |
35 | ) 36 | } 37 | }); 38 | 39 | export default Photo; 40 | -------------------------------------------------------------------------------- /18/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | 4 | const PhotoGrid = React.createClass({ 5 | render() { 6 | return ( 7 |
8 | {this.props.posts.map((post, i) => )} 9 |
10 | ) 11 | } 12 | }); 13 | 14 | export default PhotoGrid; 15 | -------------------------------------------------------------------------------- /18/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | import Comments from './Comments'; 4 | 5 | const Single = React.createClass({ 6 | render() { 7 | const { postId } = this.props.params; 8 | 9 | const i = this.props.posts.findIndex((post) => post.code === postId); 10 | const post = this.props.posts[i]; 11 | 12 | const postComments = this.props.comments[postId] || []; 13 | 14 | return ( 15 |
16 | 17 | 18 |
19 | ) 20 | } 21 | }); 22 | 23 | export default Single; 24 | -------------------------------------------------------------------------------- /18/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /18/reducers/comments.js: -------------------------------------------------------------------------------- 1 | function postComments(state = [], action) { 2 | switch(action.type){ 3 | case 'ADD_COMMENT': 4 | // return the new state with the new comment 5 | return [...state,{ 6 | user: action.author, 7 | text: action.comment 8 | }]; 9 | case 'REMOVE_COMMENT': 10 | // we need to return the new state without the deleted comment 11 | return [ 12 | // from the start to the one we want to delete 13 | ...state.slice(0,action.i), 14 | // after the deleted one, to the end 15 | ...state.slice(action.i + 1) 16 | ] 17 | default: 18 | return state; 19 | } 20 | return state; 21 | } 22 | 23 | function comments(state = [], action) { 24 | if(typeof action.postId !== 'undefined') { 25 | return { 26 | // take the current state 27 | ...state, 28 | // overwrite this post with a new one 29 | [action.postId]: postComments(state[action.postId], action) 30 | } 31 | } 32 | return state; 33 | } 34 | 35 | export default comments; 36 | -------------------------------------------------------------------------------- /18/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import posts from './posts'; 5 | import comments from './comments'; 6 | 7 | const rootReducer = combineReducers({posts, comments, routing: routerReducer }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /18/reducers/posts.js: -------------------------------------------------------------------------------- 1 | function posts(state = [], action) { 2 | switch(action.type) { 3 | case 'INCREMENT_LIKES' : 4 | console.log("Incrementing Likes!!"); 5 | const i = action.index; 6 | return [ 7 | ...state.slice(0,i), // before the one we are updating 8 | {...state[i], likes: state[i].likes + 1}, 9 | ...state.slice(i + 1), // after the one we are updating 10 | ] 11 | default: 12 | return state; 13 | } 14 | } 15 | 16 | export default posts; 17 | -------------------------------------------------------------------------------- /18/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import App from './components/App'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | import { Provider } from 'react-redux'; 16 | import store, { history } from './store'; 17 | 18 | const router = ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | 29 | render(router, document.getElementById('root')); 30 | -------------------------------------------------------------------------------- /18/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | export default store; 22 | -------------------------------------------------------------------------------- /18/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /18/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /18/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /18/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /19/actions/actionCreators.js: -------------------------------------------------------------------------------- 1 | // increment 2 | export function increment(index) { 3 | return { 4 | type: 'INCREMENT_LIKES', 5 | index 6 | } 7 | } 8 | 9 | // add comment 10 | export function addComment(postId, author, comment) { 11 | return { 12 | type: 'ADD_COMMENT', 13 | postId, 14 | author, 15 | comment 16 | } 17 | } 18 | 19 | // remove comment 20 | 21 | export function removeComment(postId, i) { 22 | return { 23 | type: 'REMOVE_COMMENT', 24 | i, 25 | postId 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /19/components/App.js: -------------------------------------------------------------------------------- 1 | import { bindActionCreators } from 'redux'; 2 | import { connect } from 'react-redux'; 3 | import * as actionCreators from '../actions/actionCreators'; 4 | import Main from './Main'; 5 | 6 | function mapStateToProps(state) { 7 | return { 8 | posts: state.posts, 9 | comments: state.comments 10 | } 11 | } 12 | 13 | function mapDispachToProps(dispatch) { 14 | return bindActionCreators(actionCreators, dispatch); 15 | } 16 | 17 | const App = connect(mapStateToProps, mapDispachToProps)(Main); 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /19/components/Comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Comments = React.createClass({ 4 | renderComment(comment, i) { 5 | return ( 6 |
7 |

8 | {comment.user} 9 | {comment.text} 10 | 11 |

12 |
13 | ) 14 | }, 15 | handleSubmit(e) { 16 | e.preventDefault(); 17 | const { postId } = this.props.params; 18 | const author = this.refs.author.value; 19 | const comment = this.refs.comment.value; 20 | this.props.addComment(postId, author, comment); 21 | this.refs.commentForm.reset(); 22 | }, 23 | render() { 24 | return ( 25 |
26 | {this.props.postComments.map(this.renderComment)} 27 |
28 | 29 | 30 | 31 |
32 |
33 | ) 34 | } 35 | }); 36 | 37 | export default Comments; 38 | -------------------------------------------------------------------------------- /19/components/Main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | 4 | const Main = React.createClass({ 5 | render() { 6 | return ( 7 |
8 |

9 | Reduxstagram 10 |

11 | {React.cloneElement({...this.props}.children, {...this.props})} 12 |
13 | ) 14 | } 15 | }); 16 | 17 | export default Main; 18 | -------------------------------------------------------------------------------- /19/components/Photo.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router'; 3 | import CSSTransitionGroup from 'react-addons-css-transition-group'; 4 | 5 | const Photo = React.createClass({ 6 | render() { 7 | const { post, i, comments } = this.props; 8 | return ( 9 |
10 |
11 | 12 | {post.caption} 13 | 14 | 15 | 16 | {post.likes} 17 | 18 | 19 |
20 | 21 |
22 |

{post.caption}

23 |
24 | 25 | 26 | 27 | 28 | {comments[post.code] ? comments[post.code].length : 0 } 29 | 30 | 31 |
32 |
33 | 34 |
35 | ) 36 | } 37 | }); 38 | 39 | export default Photo; 40 | -------------------------------------------------------------------------------- /19/components/PhotoGrid.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | 4 | const PhotoGrid = React.createClass({ 5 | render() { 6 | return ( 7 |
8 | {this.props.posts.map((post, i) => )} 9 |
10 | ) 11 | } 12 | }); 13 | 14 | export default PhotoGrid; 15 | -------------------------------------------------------------------------------- /19/components/Single.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Photo from './Photo'; 3 | import Comments from './Comments'; 4 | 5 | const Single = React.createClass({ 6 | render() { 7 | const { postId } = this.props.params; 8 | 9 | const i = this.props.posts.findIndex((post) => post.code === postId); 10 | const post = this.props.posts[i]; 11 | 12 | const postComments = this.props.comments[postId] || []; 13 | 14 | return ( 15 |
16 | 17 | 18 |
19 | ) 20 | } 21 | }); 22 | 23 | export default Single; 24 | -------------------------------------------------------------------------------- /19/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /19/reducers/comments.js: -------------------------------------------------------------------------------- 1 | function postComments(state = [], action) { 2 | switch(action.type){ 3 | case 'ADD_COMMENT': 4 | // return the new state with the new comment 5 | return [...state,{ 6 | user: action.author, 7 | text: action.comment 8 | }]; 9 | case 'REMOVE_COMMENT': 10 | // we need to return the new state without the deleted comment 11 | return [ 12 | // from the start to the one we want to delete 13 | ...state.slice(0,action.i), 14 | // after the deleted one, to the end 15 | ...state.slice(action.i + 1) 16 | ] 17 | default: 18 | return state; 19 | } 20 | return state; 21 | } 22 | 23 | function comments(state = [], action) { 24 | if(typeof action.postId !== 'undefined') { 25 | return { 26 | // take the current state 27 | ...state, 28 | // overwrite this post with a new one 29 | [action.postId]: postComments(state[action.postId], action) 30 | } 31 | } 32 | return state; 33 | } 34 | 35 | export default comments; 36 | -------------------------------------------------------------------------------- /19/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | 4 | import posts from './posts'; 5 | import comments from './comments'; 6 | 7 | const rootReducer = combineReducers({posts, comments, routing: routerReducer }); 8 | 9 | export default rootReducer; 10 | -------------------------------------------------------------------------------- /19/reducers/posts.js: -------------------------------------------------------------------------------- 1 | function posts(state = [], action) { 2 | switch(action.type) { 3 | case 'INCREMENT_LIKES' : 4 | console.log("Incrementing Likes!!"); 5 | const i = action.index; 6 | return [ 7 | ...state.slice(0,i), // before the one we are updating 8 | {...state[i], likes: state[i].likes + 1}, 9 | ...state.slice(i + 1), // after the one we are updating 10 | ] 11 | default: 12 | return state; 13 | } 14 | } 15 | 16 | export default posts; 17 | -------------------------------------------------------------------------------- /19/reduxstagram.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { render } from 'react-dom'; 4 | 5 | // Import css 6 | import css from './styles/style.styl'; 7 | 8 | // Import Components 9 | import App from './components/App'; 10 | import Single from './components/Single'; 11 | import PhotoGrid from './components/PhotoGrid'; 12 | 13 | // import react router deps 14 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 15 | import { Provider } from 'react-redux'; 16 | import store, { history } from './store'; 17 | 18 | const router = ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | 29 | render(router, document.getElementById('root')); 30 | -------------------------------------------------------------------------------- /19/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, compose } from 'redux'; 2 | import { syncHistoryWithStore} from 'react-router-redux'; 3 | import { browserHistory } from 'react-router'; 4 | 5 | // import the root reducer 6 | import rootReducer from './reducers/index'; 7 | 8 | import comments from './data/comments'; 9 | import posts from './data/posts'; 10 | 11 | // create an object for the default data 12 | const defaultState = { 13 | posts, 14 | comments 15 | }; 16 | 17 | const store = createStore(rootReducer, defaultState); 18 | 19 | export const history = syncHistoryWithStore(browserHistory, store); 20 | 21 | if(module.hot) { 22 | module.hot.accept('./reducers/',() => { 23 | const nextRootReducer = require('./reducers/index').default; 24 | store.replaceReducer(nextRootReducer); 25 | }); 26 | } 27 | 28 | export default store; 29 | -------------------------------------------------------------------------------- /19/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /19/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /19/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /19/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /learn-redux/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/learn-redux/.DS_Store -------------------------------------------------------------------------------- /learn-redux/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015"], 3 | "env": { 4 | "development": { 5 | "plugins": [ 6 | ["transform-object-rest-spread"], 7 | ["transform-react-display-name"], 8 | ["react-transform", { 9 | "transforms": [{ 10 | "transform": "react-transform-hmr", 11 | "imports": ["react"], 12 | "locals": ["module"] 13 | }, { 14 | "transform": "react-transform-catch-errors", 15 | "imports": ["react", "redbox-react"] 16 | }] 17 | }] 18 | ] 19 | }, 20 | "production": { 21 | "plugins": [ 22 | ["transform-object-rest-spread"], 23 | ["transform-react-display-name"] 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /learn-redux/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "jsx": true, 4 | "modules": true 5 | }, 6 | "env": { 7 | "browser": true, 8 | "node": true 9 | }, 10 | "parser": "babel-eslint", 11 | "rules": { 12 | "quotes": [2, "single"], 13 | "strict": [2, "never"], 14 | "babel/generator-star-spacing": 1, 15 | "babel/new-cap": 1, 16 | "babel/object-shorthand": 1, 17 | "babel/arrow-parens": 1, 18 | "babel/no-await-in-loop": 1, 19 | "react/jsx-uses-react": 2, 20 | "react/jsx-uses-vars": 2, 21 | "react/react-in-jsx-scope": 2 22 | }, 23 | "plugins": [ 24 | "babel", 25 | "react" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /learn-redux/client/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wesbos/Learn-Redux-Starter-Files/38d37295ca7bb47cb20c92b74140e5b2ff6c122d/learn-redux/client/.DS_Store -------------------------------------------------------------------------------- /learn-redux/client/data/config.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven-js'; 2 | 3 | const sentry_key = 'cb55d4f05cd443ce82303222f77ef5e0'; 4 | const sentry_app = '61499'; 5 | export const sentry_url = `https://${sentry_key}@app.getsentry.com/${sentry_app}`; 6 | 7 | export function logException(ex, context) { 8 | Raven.captureException(ex, { 9 | extra: context 10 | }); 11 | /*eslint no-console:0*/ 12 | window && window.console && console.error && console.error(ex); 13 | } 14 | -------------------------------------------------------------------------------- /learn-redux/client/reduxstagram.js: -------------------------------------------------------------------------------- 1 | // let's go! 2 | -------------------------------------------------------------------------------- /learn-redux/client/styles/_animations.styl: -------------------------------------------------------------------------------- 1 | // offset variable gets tacked for centering in addition to the scaling 2 | 3 | offsets = translateX(-50%) translateY(-50%) 4 | .likes-heart 5 | opacity 0 6 | transition all 0.5s // time to fade out after its done 7 | transform offsets scale(5) // this is the "end state" 8 | display block 9 | &.like-enter 10 | transition all .2s 11 | transform offsets scale(1) 12 | opacity 1 13 | &.like-enter-active 14 | transform offsets scale(5) 15 | .like-leave-active 16 | display none 17 | 18 | -------------------------------------------------------------------------------- /learn-redux/client/styles/_normalize.styl: -------------------------------------------------------------------------------- 1 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button],/* 1 */ 2 | input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 3 | 4 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 5 | 6 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 7 | 8 | 9 | img 10 | max-width 100% 11 | -------------------------------------------------------------------------------- /learn-redux/client/styles/_typography.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Variables 3 | */ 4 | 5 | blue = #125688 6 | offwhite = #fafafa 7 | lightgrey = #EDEEED 8 | lightgray = lightgrey // OH Canada! 9 | 10 | html 11 | font-size 10px 12 | font-family sans-serif 13 | 14 | p 15 | font-size 1.6rem 16 | line-height 1.5 17 | 18 | h1 19 | font-family billabong, 'billabongregular' 20 | text-align center 21 | font-weight 100 22 | font-size 13rem 23 | margin 2rem 0 24 | letter-spacing -1px 25 | text-shadow 0px 4px 0 rgba(18, 86, 136, 0.11) 26 | a 27 | color blue 28 | text-decoration none 29 | &:focus 30 | outline 0 31 | 32 | 33 | // "Instagram-like" webfont 34 | 35 | @font-face { 36 | font-family: 'billabongregular'; 37 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot'); 38 | src: url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.eot?#iefix') format('embedded-opentype'), 39 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.woff') format('woff'), 40 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.ttf') format('truetype'), 41 | url('https://cdn.rawgit.com/milktronics/beaglegr.am/master/public/fonts/billabong-webfont.svg#billabongregular') format('svg'); 42 | font-weight: normal; 43 | font-style: normal; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /learn-redux/client/styles/style.styl: -------------------------------------------------------------------------------- 1 | @import '_normalize.styl' 2 | @import '_typography.styl' 3 | @import '_animations.styl' 4 | 5 | body 6 | background offwhite 7 | 8 | .photo-grid 9 | display flex 10 | flex-wrap wrap 11 | max-width 1200px 12 | margin 0 auto 13 | 14 | .grid-figure 15 | flex-basis calc(33.333% - 4rem) 16 | flex-grow 1 17 | flex-shrink 0 18 | margin 0 2rem 2rem 2rem 19 | padding 2rem 20 | border 1px solid lightgray 21 | background white 22 | box-shadow 0 0 0 5px rgba(0,0,0,0.03); 23 | position relative 24 | 25 | .single-photo 26 | @extend .grid-figure 27 | max-width 900px 28 | margin 0 auto 29 | display flex 30 | background white 31 | .grid-figure 32 | box-shadow none 33 | margin 0 2rem 0 0 34 | border 0 35 | padding 0 36 | flex 1 0 60% 37 | max-width 60% 38 | .comments 39 | flex 1 0 40% 40 | max-width 40% 41 | .grid-photo 42 | width 100% 43 | margin 0 44 | 45 | .grid-photo 46 | width calc(100% + 4rem) 47 | margin-left -2rem 48 | margin-top -2rem 49 | max-width none 50 | 51 | // Comments 52 | .remove-comment 53 | background none 54 | border 0 55 | line-height 1 56 | opacity 0 57 | &:hover 58 | color red 59 | 60 | .comment 61 | border-bottom 1px solid lightgrey 62 | padding 0.5rem 0 63 | p 64 | font-size 1.2rem 65 | margin 0 66 | strong 67 | color blue 68 | margin-right 5px 69 | &:hover 70 | .remove-comment 71 | opacity 1 72 | 73 | .comment-form 74 | input, textarea 75 | width 100% 76 | border 0 77 | font-size 1.3rem 78 | padding 1rem 0 79 | border-bottom 1px solid lightgrey 80 | outline none 81 | resize vertical 82 | 83 | 84 | .grid-photo-wrap 85 | position relative 86 | 87 | .likes-heart 88 | background url(http://f.cl.ly/items/3Y373q2Q3J3Y1j203n0m/Bitmap-3.png) center no-repeat 89 | background-size contain 90 | font-size 2rem 91 | padding 1rem 92 | position absolute 93 | color blue 94 | left 50% 95 | top 50% 96 | pointer-events none 97 | 98 | 99 | /* 100 | Buttons 101 | */ 102 | 103 | .control-buttons 104 | display flex 105 | justify-content space-between 106 | 107 | button, .button 108 | border 2px solid lighten(grey, 90%) 109 | background none 110 | flex-basis 48% 111 | display inline-block 112 | line-height 2 113 | text-decoration none 114 | padding 5px 115 | text-align center 116 | font-size 15px 117 | color blue 118 | transition all 0.2s 119 | box-sizing padding-box 120 | &:hover, &:focus 121 | border-color blue 122 | outline 0 123 | 124 | /* 125 | Cowboy style speech bubble - you should probably use an SVG for this if you are doing more icons. 126 | */ 127 | .speech-bubble 128 | size = 1.25rem 129 | width size * 1.2 130 | height size 131 | background blue 132 | display inline-block 133 | border-radius 50% 134 | position relative 135 | &:after 136 | display inline-block 137 | position absolute 138 | content '' 139 | width: 0; 140 | height: 0; 141 | border-style: solid; 142 | border-width: 0 size size 0 143 | border-color: transparent blue transparent transparent 144 | top 30% 145 | left 0 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /learn-redux/devServer.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var webpack = require('webpack'); 4 | var config = require('./webpack.config.dev'); 5 | 6 | var app = express(); 7 | var compiler = webpack(config); 8 | 9 | app.use(require('webpack-dev-middleware')(compiler, { 10 | noInfo: true, 11 | publicPath: config.output.publicPath 12 | })); 13 | 14 | app.use(require('webpack-hot-middleware')(compiler)); 15 | 16 | app.get('*', function(req, res) { 17 | res.sendFile(path.join(__dirname, 'index.html')); 18 | }); 19 | 20 | app.listen(7770, 'localhost', function(err) { 21 | if (err) { 22 | console.log(err); 23 | return; 24 | } 25 | 26 | console.log('Listening at http://localhost:7770'); 27 | }); 28 | -------------------------------------------------------------------------------- /learn-redux/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Reduxstagram 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /learn-redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-redux", 3 | "version": "1.0.0", 4 | "description": ":) ", 5 | "scripts": { 6 | "build:webpack": "NODE_ENV=production webpack --config webpack.config.prod.js", 7 | "build": "npm run clean && npm run build:webpack", 8 | "test": "NODE_ENV=production mocha './tests/**/*.spec.js' --compilers js:babel-core/register", 9 | "clean": "rimraf dist", 10 | "start": "node devServer.js", 11 | "tunnel": "browser-sync start --proxy localhost:7770 --tunnel wesbos" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/wesbos/Learn-Redux.git" 16 | }, 17 | "author": "Wes Bos", 18 | "license": "MIT", 19 | "homepage": "https://github.com/wesbos/Learn-Redux", 20 | "dependencies": { 21 | "babel-core": "^6.7.7", 22 | "babel-eslint": "^6.0.4", 23 | "babel-loader": "^6.2.4", 24 | "babel-plugin-react-transform": "^2.0.2", 25 | "babel-plugin-transform-object-rest-spread": "^6.6.5", 26 | "babel-plugin-transform-react-display-name": "^6.5.0", 27 | "babel-polyfill": "^6.7.4", 28 | "babel-preset-es2015": "^6.6.0", 29 | "babel-preset-react": "^6.5.0", 30 | "css-loader": "^0.23.1", 31 | "eslint": "^2.9.0", 32 | "eslint-plugin-babel": "^3.2.0", 33 | "eslint-plugin-react": "^5.0.1", 34 | "express": "^4.13.4", 35 | "raven-js": "^2.3.0", 36 | "react": "^15.0.2", 37 | "react-addons-css-transition-group": "^15.0.2", 38 | "react-dom": "^15.0.2", 39 | "react-redux": "^4.4.5", 40 | "react-router": "^2.4.0", 41 | "react-router-redux": "^4.0.4", 42 | "react-transform-catch-errors": "^1.0.2", 43 | "react-transform-hmr": "^1.0.4", 44 | "redbox-react": "^1.2.3", 45 | "redux": "^3.5.2", 46 | "rimraf": "^2.5.2", 47 | "style-loader": "^0.13.1", 48 | "stylus": "^0.54.5", 49 | "stylus-loader": "^2.0.0", 50 | "webpack": "^1.13.0", 51 | "webpack-dev-middleware": "^1.6.1", 52 | "webpack-hot-middleware": "^2.10.0" 53 | }, 54 | "devDependencies": { 55 | "expect": "^1.18.0", 56 | "expect-jsx": "^2.5.1", 57 | "mocha": "^2.4.5", 58 | "react-addons-test-utils": "^15.0.2" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /learn-redux/readme.md: -------------------------------------------------------------------------------- 1 | # Learn Redux 2 | 3 | A simple React + Redux implementation. This will be turned into a free video series once the app is totally fleshed out. 4 | 5 | ## Running 6 | 7 | First `npm install` to grab all the necessary dependencies. 8 | 9 | Then run `npm start` and open in your browser. 10 | 11 | ## Production Build 12 | 13 | Run `npm build` to create a distro folder and a bundle.js file. 14 | -------------------------------------------------------------------------------- /learn-redux/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'source-map', 6 | entry: [ 7 | 'webpack-hot-middleware/client', 8 | './client/reduxstagram' 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'dist'), 12 | filename: 'bundle.js', 13 | publicPath: '/static/' 14 | }, 15 | plugins: [ 16 | new webpack.HotModuleReplacementPlugin(), 17 | new webpack.NoErrorsPlugin() 18 | ], 19 | module: { 20 | loaders: [ 21 | // js 22 | { 23 | test: /\.js$/, 24 | loaders: ['babel'], 25 | include: path.join(__dirname, 'client') 26 | }, 27 | // CSS 28 | { 29 | test: /\.styl$/, 30 | include: path.join(__dirname, 'client'), 31 | loader: 'style-loader!css-loader!stylus-loader' 32 | } 33 | ] 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /learn-redux/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | module.exports = { 5 | devtool: 'source-map', 6 | entry: [ 7 | 8 | './client/reduxstagram' 9 | ], 10 | output: { 11 | path: path.join(__dirname, 'dist'), 12 | filename: 'bundle.js', 13 | publicPath: '/static/' 14 | }, 15 | plugins: [ 16 | new webpack.optimize.OccurenceOrderPlugin(), 17 | new webpack.DefinePlugin({ 18 | 'process.env': { 19 | 'NODE_ENV': "'production'" 20 | } 21 | }), 22 | new webpack.optimize.UglifyJsPlugin({ 23 | compressor: { 24 | warnings: false 25 | } 26 | }) 27 | ], 28 | module: { 29 | loaders: [ 30 | // js 31 | { 32 | test: /\.js$/, 33 | loaders: ['babel'], 34 | include: path.join(__dirname, 'client') 35 | }, 36 | // CSS 37 | { 38 | test: /\.styl$/, 39 | include: path.join(__dirname, 'client'), 40 | loader: 'style-loader!css-loader!stylus-loader' 41 | } 42 | ] 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Learn Redux Starter files 2 | 3 | This repo includes the `learn-redux` folder which is where you will build your application as well as a number of partially finished `client` folder contents that correspond with the **start** of each video. These stepped folders should be used to reference or restore your application if things aren't working 100%. 4 | 5 | There are only folders for videos which have significant changes. 6 | 7 | ## Getting Started 8 | 9 | Fork or download this repo and run `npm install` in the `learn-redux` folder with the `package.json` file. 10 | 11 | 12 | ## Pull Requests 13 | 14 | Pull requests that fix dependencies between the videos and this repo are welcome as long as they correspond with the code written in the videos. 15 | 16 | 17 | ## FAQ / Help 18 | 19 | **Before anything**, make sure you are on the latest node. You can run `node -v` and if you have anything less than `5.x`, you'll need to update. The files will probably work on older versions, but in my experience 80% of issues go away with an update. 20 | 21 | ### Q: I'm getting: _warning.js:44 Warning: Main: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop._ 22 | 23 | 24 | **A:**: I haven't looked into why I didn't get this error, but it means that you should not pass down `key` or `ref` because those are props that are tied and unique to each element. For now, there is a [nice fix posted here](https://github.com/wesbos/Learn-Redux-Starter-Files/issues/6#issuecomment-222210005) 25 | 26 | ### Q: I'm getting Unexpected Token Error 27 | 28 | **A:** You probably don't have the `.babelrc` file in your `learn-redux` folder. This makes sure that you have all the right transpile dependencies. [Grab it from here](https://github.com/wesbos/Learn-Redux-Starter-Files/blob/master/learn-redux/.babelrc). 29 | 30 | ### Q: How do I download these videos? 31 | 32 | **A:** I made a video for [exactly this](https://www.youtube.com/watch?v=-eUd2k5M1B0). 33 | 34 | ### Q: What theme and font are you using? 35 | 36 | **A:** Cobalt2 and Operator Mono. I wrote a bit of info here → 37 | 38 | 39 | --------------------------------------------------------------------------------