├── .babelrc ├── .editorconfig ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── app.js ├── db.sql ├── less-watch-compiler.config.json ├── models ├── db.js ├── mail.js ├── middlewares.js ├── mysql.js └── userFn.js ├── package-lock.json ├── package.json ├── public ├── images │ ├── error-3.svg │ ├── favicon │ │ └── favicon.png │ ├── images (11).png │ ├── large.jpg │ ├── spacecraft.jpg │ └── tumblr_nk2y2oVEjp1rn9vmdo1_500.gif ├── js │ ├── dist │ │ └── bundle.js │ └── src │ │ ├── actions │ │ ├── explore-action.js │ │ ├── follow-action.js │ │ ├── note-int-action.js │ │ ├── notes-action.js │ │ └── user-action.js │ │ ├── components │ │ ├── app-comp.js │ │ ├── deactivate │ │ │ └── deactivate-comp.js │ │ ├── edit │ │ │ └── edit-comp.js │ │ ├── email-verification │ │ │ └── email-ver-comp.js │ │ ├── error │ │ │ └── error-comp.js │ │ ├── explore │ │ │ ├── explore-comp.js │ │ │ └── explores-list.js │ │ ├── home │ │ │ ├── feeds-comp.js │ │ │ └── home-comp.js │ │ ├── note │ │ │ ├── create-note-comp.js │ │ │ ├── like-items.js │ │ │ ├── likes-comp.js │ │ │ ├── note-comp.js │ │ │ └── view-note-comp.js │ │ ├── others │ │ │ ├── end-comp.js │ │ │ ├── goto-comp.js │ │ │ ├── header-comp.js │ │ │ ├── nothing-comp.js │ │ │ ├── overlay-comp.js │ │ │ ├── prompt-comp.js │ │ │ └── title-comp.js │ │ └── profile │ │ │ ├── banner-comp.js │ │ │ ├── filter-notes-comp.js │ │ │ ├── follow │ │ │ ├── follower-items.js │ │ │ ├── followers-comp.js │ │ │ ├── following-items.js │ │ │ └── followings-comp.js │ │ │ ├── notes-comp.js │ │ │ └── profile-comp.js │ │ ├── functions │ │ └── functions.js │ │ ├── main.js │ │ ├── reducers │ │ ├── explore-reducer.js │ │ ├── follow-reducer.js │ │ ├── note-int-reducer.js │ │ ├── notes-reducer.js │ │ └── user-reducer.js │ │ ├── store │ │ └── store.js │ │ └── user-system │ │ └── user-system.js ├── styles │ ├── dist │ │ ├── perfect-scrollbar.css │ │ └── styles.css │ └── src │ │ ├── defaults.less │ │ └── styles.less ├── temp │ ├── .gitkeep │ └── about-this-folder.txt └── users │ ├── 5 │ └── user.jpg │ ├── 6 │ ├── smoke.jpg │ └── user.jpg │ ├── 7 │ └── user.jpg │ ├── 8 │ └── user.jpg │ └── about_this_folder.txt ├── routes ├── api-routes.js ├── edit-routes.js ├── follow-routes.js ├── main-routes.js ├── note-int-routes.js ├── note_routes.js └── user-routes.js ├── screenshots ├── Snap 2017-07-27 at 00.27.11.png ├── Snap 2017-07-27 at 00.27.24.png ├── Snap 2017-07-27 at 00.27.34.png ├── Snap 2017-07-27 at 00.27.45.png ├── Snap 2017-07-27 at 00.28.54.png ├── Snap 2017-07-27 at 00.29.13.png ├── Snap 2017-07-27 at 00.29.35.png ├── Snap 2017-07-27 at 00.30.13.png ├── Snap 2017-07-27 at 00.31.06.png └── Snap 2017-09-17 at 13.30.18.png ├── tsconfig.json ├── views ├── 404.hbs ├── app.hbs ├── login.hbs ├── partials │ ├── footer.hbs │ ├── nHeader.hbs │ ├── needs.hbs │ ├── tophead.hbs │ └── yHeader.hbs ├── registered.hbs ├── signup.hbs └── welcome.hbs ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env", 4 | "react", 5 | "stage-0" 6 | ], 7 | "plugins": [ 8 | "react-html-attrs", 9 | "transform-class-properties", 10 | "transform-decorators-legacy", 11 | "transform-react-jsx-source", 12 | ["transform-runtime", {"polyfill": false, "regenerator": true }] 13 | ] 14 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http.editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .env 3 | .vscode 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Faiyaz Shaikh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Single-Page-SocialNetwork 2 | A reactive single-page-social-network created with React. Screenshots below!! 3 | 4 | # Quick liks 5 | 1. [Screenshots](#screenshots) 6 | 2. [Own the project](#own-the-project) 7 | 8 | # Screenshots 9 | ![alt text](https://raw.githubusercontent.com/yTakkar/Single-Page-Social-Network/master/screenshots/Snap%202017-07-27%20at%2000.27.11.png) 10 | ![alt text](https://raw.githubusercontent.com/yTakkar/Single-Page-Social-Network/master/screenshots/Snap%202017-07-27%20at%2000.27.24.png) 11 | ![alt text](https://raw.githubusercontent.com/yTakkar/Single-Page-Social-Network/master/screenshots/Snap%202017-07-27%20at%2000.27.34.png) 12 | ![alt text](https://raw.githubusercontent.com/yTakkar/Single-Page-Social-Network/master/screenshots/Snap%202017-07-27%20at%2000.27.45.png) 13 | ![alt text](https://raw.githubusercontent.com/yTakkar/Single-Page-Social-Network/master/screenshots/Snap%202017-07-27%20at%2000.29.13.png) 14 | ![alt text](https://raw.githubusercontent.com/yTakkar/Single-Page-Social-Network/master/screenshots/Snap%202017-07-27%20at%2000.31.06.png) 15 | ![alt text](https://raw.githubusercontent.com/yTakkar/Single-Page-Social-Network/15f084078b23b862a7537adbc721623e0b81578d/screenshots/Snap%202017-09-17%20at%2013.30.18.png) 16 | ![alt text](https://raw.githubusercontent.com/yTakkar/Single-Page-Social-Network/master/screenshots/Snap%202017-07-27%20at%2000.29.35.png) 17 | ![alt text](https://raw.githubusercontent.com/yTakkar/Single-Page-Social-Network/master/screenshots/Snap%202017-07-27%20at%2000.28.54.png) 18 | 19 | # Own the project 20 | 1. First install all dependencies: 21 | ```bash 22 | # with npm 23 | npm install 24 | 25 | # or with yarn 26 | yarn 27 | ``` 28 | 29 | 2. Open PHPMyAdmin, create a DB & import `db.sql` file. 30 | 3. Create a `.env` file and insert the following code. Replace values with yours!! 31 | 32 | ```javascript 33 | PORT=YOUR_PORT 34 | MYSQL_HOST="host" 35 | MYSQL_USER="user" 36 | MYSQL_PASSWORD="password" 37 | MYSQL_DATABASE="db" 38 | MAIL="yourgmail@gmail.com" 39 | MAIL_PASSWORD="gmail-password" 40 | SESSION_SECRET_LETTER="anything-secret" 41 | ``` 42 | 43 | 4. Start the server 44 | ```javascript 45 | npm start 46 | ``` 47 | 48 | 5. Now run the app 49 | ```javacript 50 | localhost:[PORT] PORT = YOU DEFINED IN .ENV FILE. 1157 BY DEFAULT!! 51 | ``` 52 | 53 | 6. Enjoy!! 54 | 55 | # Contribute 56 | 57 | Jason Alava 58 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | // Installed packages 4 | const 5 | express = require('express'), 6 | { env: { PORT, SESSION_SECRET_LETTER } } = process, 7 | hbs = require('express-handlebars'), 8 | path = require('path'), 9 | logger = require('morgan'), 10 | favicon = require('serve-favicon'), 11 | bodyParser = require('body-parser'), 12 | validator = require('express-validator'), 13 | session = require('client-sessions'), 14 | hl = require('handy-log'), 15 | app = express() 16 | 17 | // Requiring project files 18 | const 19 | uRoutes = require('./routes/user-routes'), 20 | apiRoutes = require('./routes/api-routes'), 21 | mRoutes = require('./routes/main-routes'), 22 | followRoutes = require('./routes/follow-routes'), 23 | noteRoutes = require('./routes/note_routes'), 24 | nIntRoutes = require('./routes/note-int-routes'), 25 | editRoutes = require('./routes/edit-routes'), 26 | mw = require('./models/middlewares') 27 | 28 | // View engine 29 | app.engine('hbs', hbs({ 30 | extname: "hbs" 31 | })) 32 | app.set('view engine', 'hbs') 33 | 34 | // Middlewares 35 | app.use(favicon( 36 | path.join(__dirname + "/public/images/favicon/favicon.png") 37 | )) 38 | // app.use(logger("dev")) 39 | app.use(bodyParser.json()) 40 | app.use(bodyParser.urlencoded({ 41 | extended: false 42 | })) 43 | app.use(validator()) 44 | app.use(session({ 45 | cookieName: "session", 46 | secret: SESSION_SECRET_LETTER, 47 | duration: 60 * 60 * 1000, 48 | activeDuration: 5 * 60 * 1000 49 | })) 50 | app.use(express.static(path.join(__dirname + "/public/"))) 51 | 52 | // Middleware for some local variables to be used in the template 53 | app.use(mw.variables) 54 | 55 | // Route files (Order is important) 56 | app.use('/', uRoutes) 57 | app.use('/api', apiRoutes) 58 | app.use('/api', followRoutes) 59 | app.use('/api', noteRoutes) 60 | app.use('/api', nIntRoutes) 61 | app.use('/api', editRoutes) 62 | app.use('/', mRoutes) 63 | 64 | app.listen(PORT, () => hl.rainbow('App running..')) 65 | -------------------------------------------------------------------------------- /db.sql: -------------------------------------------------------------------------------- 1 | -- phpMyAdmin SQL Dump 2 | -- version 4.5.1 3 | -- http://www.phpmyadmin.net 4 | -- 5 | -- Host: 127.0.0.1 6 | -- Generation Time: Aug 16, 2017 at 01:35 PM 7 | -- Server version: 10.1.19-MariaDB 8 | -- PHP Version: 5.6.28 9 | 10 | SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; 11 | SET time_zone = "+00:00"; 12 | 13 | 14 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 15 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 16 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 17 | /*!40101 SET NAMES utf8mb4 */; 18 | 19 | -- 20 | -- Database: `notesapp` 21 | -- 22 | 23 | -- -------------------------------------------------------- 24 | 25 | -- 26 | -- Table structure for table `follow_system` 27 | -- 28 | 29 | CREATE TABLE `follow_system` ( 30 | `follow_id` int(11) NOT NULL, 31 | `follow_by` int(11) NOT NULL, 32 | `follow_by_username` varchar(32) NOT NULL, 33 | `follow_to` int(11) NOT NULL, 34 | `follow_to_username` varchar(32) NOT NULL, 35 | `follow_time` varchar(100) NOT NULL 36 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 37 | 38 | -- 39 | -- Dumping data for table `follow_system` 40 | -- 41 | 42 | INSERT INTO `follow_system` (`follow_id`, `follow_by`, `follow_by_username`, `follow_to`, `follow_to_username`, `follow_time`) VALUES 43 | (14, 8, 'coldplay', 6, 'faiyaz', '1501010150463'), 44 | (16, 8, 'coldplay', 5, 'takkar', '1501011954639'), 45 | (29, 7, 'ghalib', 5, 'takkar', '1501092849849'), 46 | (39, 5, 'takkar', 6, 'faiyaz', '1502801315146'), 47 | (43, 5, 'takkar', 7, 'ghalib', '1502801363666'); 48 | 49 | -- -------------------------------------------------------- 50 | 51 | -- 52 | -- Table structure for table `likes` 53 | -- 54 | 55 | CREATE TABLE `likes` ( 56 | `like_id` int(11) NOT NULL, 57 | `like_by` int(11) NOT NULL, 58 | `like_by_username` varchar(32) NOT NULL, 59 | `note_id` int(11) NOT NULL, 60 | `like_time` varchar(100) NOT NULL 61 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 62 | 63 | -- -------------------------------------------------------- 64 | 65 | -- 66 | -- Table structure for table `notes` 67 | -- 68 | 69 | CREATE TABLE `notes` ( 70 | `note_id` int(11) NOT NULL, 71 | `user` int(11) NOT NULL, 72 | `username` varchar(32) NOT NULL, 73 | `title` varchar(200) NOT NULL, 74 | `content` text NOT NULL, 75 | `note_time` varchar(200) NOT NULL 76 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 77 | 78 | -- 79 | -- Dumping data for table `notes` 80 | -- 81 | 82 | INSERT INTO `notes` (`note_id`, `user`, `username`, `title`, `content`, `note_time`) VALUES 83 | (61, 6, 'faiyaz', 'Untitled note', 'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry''s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.', '1497022376821'), 84 | (70, 6, 'faiyaz', 'Miracle', 'For you!! and me', '1497043496669'), 85 | (73, 8, 'coldplay', 'coldplay', '.....', '1497279792645'), 86 | (74, 8, 'coldplay', 'k', 'k', '1497279939388'), 87 | (75, 7, 'ghalib', 'Note...', '???????', '1497357617034'), 88 | (100, 5, 'takkar', 'one', 'j', '1502732101700'), 89 | (101, 5, 'takkar', 'two', 'jj', '1502732106616'), 90 | (102, 5, 'takkar', 'three', 'jjkmkm', '1502732115644'), 91 | (103, 5, 'takkar', 'fourrrr', 'g', '1502732123371'), 92 | (132, 5, 'takkar', 'five', 'hj', '1502879932911'), 93 | (141, 5, 'takkar', 'six', 'h', '1502882974135'); 94 | 95 | -- -------------------------------------------------------- 96 | 97 | -- 98 | -- Table structure for table `profile_views` 99 | -- 100 | 101 | CREATE TABLE `profile_views` ( 102 | `view_id` int(11) NOT NULL, 103 | `view_by` int(11) NOT NULL, 104 | `view_by_username` varchar(32) NOT NULL, 105 | `view_to` int(11) NOT NULL, 106 | `view_time` varchar(100) NOT NULL 107 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 108 | 109 | -- 110 | -- Dumping data for table `profile_views` 111 | -- 112 | 113 | INSERT INTO `profile_views` (`view_id`, `view_by`, `view_by_username`, `view_to`, `view_time`) VALUES 114 | (32, 5, 'takkar', 6, '1500921835026'), 115 | (33, 5, 'takkar', 6, '1500922249480'), 116 | (34, 5, 'takkar', 7, '1500924838877'), 117 | (35, 5, 'takkar', 8, '1500924855217'), 118 | (36, 5, 'takkar', 6, '1500992772869'), 119 | (37, 5, 'takkar', 6, '1500992897841'), 120 | (38, 5, 'takkar', 6, '1500993317753'), 121 | (39, 5, 'takkar', 6, '1500993507892'), 122 | (40, 5, 'takkar', 6, '1500993649374'), 123 | (41, 5, 'takkar', 6, '1500993826237'), 124 | (42, 5, 'takkar', 6, '1500994322811'), 125 | (43, 5, 'takkar', 6, '1500994543604'), 126 | (44, 5, 'takkar', 6, '1500995117840'), 127 | (45, 5, 'takkar', 6, '1500995653209'), 128 | (46, 5, 'takkar', 6, '1500995874191'), 129 | (47, 5, 'takkar', 6, '1500996103844'), 130 | (48, 5, 'takkar', 6, '1500996236677'), 131 | (49, 5, 'takkar', 6, '1500996654489'), 132 | (50, 5, 'takkar', 6, '1500996960369'), 133 | (51, 5, 'takkar', 6, '1500997489661'), 134 | (52, 5, 'takkar', 6, '1500998036025'), 135 | (53, 5, 'takkar', 6, '1500998158919'), 136 | (54, 5, 'takkar', 6, '1500998352473'), 137 | (55, 5, 'takkar', 6, '1500998472529'), 138 | (56, 5, 'takkar', 6, '1500998757389'), 139 | (57, 5, 'takkar', 6, '1500999316680'), 140 | (58, 5, 'takkar', 6, '1500999632354'), 141 | (59, 5, 'takkar', 6, '1501000899577'), 142 | (60, 5, 'takkar', 6, '1501001027676'), 143 | (61, 5, 'takkar', 6, '1501001186939'), 144 | (62, 5, 'takkar', 6, '1501001347797'), 145 | (63, 5, 'takkar', 6, '1501002114586'), 146 | (64, 5, 'takkar', 6, '1501002397194'), 147 | (65, 5, 'takkar', 6, '1501002586114'), 148 | (66, 5, 'takkar', 6, '1501002963656'), 149 | (67, 5, 'takkar', 6, '1501003095544'), 150 | (68, 5, 'takkar', 6, '1501003321896'), 151 | (69, 5, 'takkar', 6, '1501003622145'), 152 | (70, 5, 'takkar', 6, '1501003988175'), 153 | (71, 5, 'takkar', 6, '1501004137852'), 154 | (72, 5, 'takkar', 6, '1501004427165'), 155 | (73, 5, 'takkar', 6, '1501004628136'), 156 | (74, 5, 'takkar', 6, '1501004997477'), 157 | (75, 5, 'takkar', 6, '1501005242285'), 158 | (76, 5, 'takkar', 6, '1501009977584'), 159 | (77, 8, 'faiyaz', 6, '1501010011470'), 160 | (78, 8, 'faiyaz', 6, '1501010143417'), 161 | (79, 8, 'faiyaz', 6, '1501010373553'), 162 | (80, 8, 'faiyaz', 6, '1501010572762'), 163 | (81, 8, 'faiyaz', 6, '1501010818531'), 164 | (82, 8, 'takkar', 5, '1501010836673'), 165 | (83, 8, 'faiyaz', 6, '1501010987216'), 166 | (84, 8, 'faiyaz', 6, '1501011297474'), 167 | (85, 8, 'faiyaz', 6, '1501011632772'), 168 | (86, 8, 'takkar', 5, '1501011651818'), 169 | (87, 8, 'faiyaz', 6, '1501011948005'), 170 | (88, 5, 'takkar', 6, '1501060667169'), 171 | (89, 5, 'takkar', 8, '1501062832435'), 172 | (90, 5, 'takkar', 7, '1501063708764'), 173 | (91, 5, 'takkar', 6, '1501078211644'), 174 | (92, 5, 'takkar', 7, '1501078221725'), 175 | (93, 5, 'takkar', 8, '1501078229858'), 176 | (94, 5, 'takkar', 8, '1501078712816'), 177 | (95, 5, 'takkar', 6, '1501078887815'), 178 | (96, 5, 'takkar', 7, '1501078891603'), 179 | (97, 5, 'takkar', 8, '1501078894745'), 180 | (98, 9, 'ghalib', 7, '1501230682653'), 181 | (99, 5, 'takkar', 7, '1501230762107'), 182 | (100, 5, 'takkar', 8, '1501253259906'), 183 | (101, 5, 'takkar', 7, '1501254287942'), 184 | (102, 5, 'takkar', 7, '1501254506340'), 185 | (103, 5, 'faiyaz', 6, '1501268109623'), 186 | (104, 5, 'faiyaz', 6, '1501268215897'), 187 | (105, 7, 'takkar', 5, '1501513737354'), 188 | (106, 5, 'ghalib', 7, '1501515252540'), 189 | (107, 5, 'faiyaz', 6, '1502451381587'), 190 | (108, 5, 'faiyaz', 6, '1502451550839'), 191 | (109, 5, 'faiyaz', 6, '1502451732859'), 192 | (110, 5, 'ghalib', 7, '1502800636947'), 193 | (111, 5, 'faiyaz', 6, '1502819156273'), 194 | (112, 5, 'faiyaz', 6, '1502880013682'); 195 | 196 | -- -------------------------------------------------------- 197 | 198 | -- 199 | -- Table structure for table `users` 200 | -- 201 | 202 | CREATE TABLE `users` ( 203 | `id` int(11) NOT NULL, 204 | `username` varchar(32) NOT NULL, 205 | `email` varchar(500) NOT NULL, 206 | `password` varchar(500) NOT NULL, 207 | `bio` varchar(500) NOT NULL, 208 | `email_verified` enum('yes','no') NOT NULL DEFAULT 'no', 209 | `joined` varchar(100) NOT NULL 210 | ) ENGINE=InnoDB DEFAULT CHARSET=latin1; 211 | 212 | -- 213 | -- Dumping data for table `users` 214 | -- 215 | 216 | INSERT INTO `users` (`id`, `username`, `email`, `password`, `bio`, `email_verified`, `joined`) VALUES 217 | (5, 'takkar', 'www.shtakkar@gmail.com', '$2a$10$sdy1uFQqpBtk8QbAQ61YqehQ73gksod9G8XbA3fqv7lcPEkffgDla', 'Developer of Instagram.', 'yes', '1497128558168'), 218 | (6, 'faiyaz', 'faiyaz@gmail.com', '$2a$10$HK.w9QLoBkkp2Tfx12FxKuaJWj2BkBPCR17xZKfk3sJFIVWfj7hma', 'Hello world!!', 'yes', '1497128554668'), 219 | (7, 'ghalib', 'ghalib@gmail.com', '$2a$10$S22pBWFlb1t1ZZnNr1pAFOVYBmbo7t.dNdD9JfB0sPsec87sEVsi.', '', 'yes', '1497128558668'), 220 | (8, 'coldplay', 'coldplay@gmail.com', '$2a$10$vSjEiBgSckcyBjZCV1AdV.x7e4n5jMte3wBlUWAH1GIEtVMfWlGdW', '', 'yes', '1497279682801'); 221 | 222 | -- 223 | -- Indexes for dumped tables 224 | -- 225 | 226 | -- 227 | -- Indexes for table `follow_system` 228 | -- 229 | ALTER TABLE `follow_system` 230 | ADD PRIMARY KEY (`follow_id`); 231 | 232 | -- 233 | -- Indexes for table `likes` 234 | -- 235 | ALTER TABLE `likes` 236 | ADD PRIMARY KEY (`like_id`); 237 | 238 | -- 239 | -- Indexes for table `notes` 240 | -- 241 | ALTER TABLE `notes` 242 | ADD PRIMARY KEY (`note_id`); 243 | 244 | -- 245 | -- Indexes for table `profile_views` 246 | -- 247 | ALTER TABLE `profile_views` 248 | ADD PRIMARY KEY (`view_id`); 249 | 250 | -- 251 | -- Indexes for table `users` 252 | -- 253 | ALTER TABLE `users` 254 | ADD PRIMARY KEY (`id`); 255 | 256 | -- 257 | -- AUTO_INCREMENT for dumped tables 258 | -- 259 | 260 | -- 261 | -- AUTO_INCREMENT for table `follow_system` 262 | -- 263 | ALTER TABLE `follow_system` 264 | MODIFY `follow_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=44; 265 | -- 266 | -- AUTO_INCREMENT for table `likes` 267 | -- 268 | ALTER TABLE `likes` 269 | MODIFY `like_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=105; 270 | -- 271 | -- AUTO_INCREMENT for table `notes` 272 | -- 273 | ALTER TABLE `notes` 274 | MODIFY `note_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=147; 275 | -- 276 | -- AUTO_INCREMENT for table `profile_views` 277 | -- 278 | ALTER TABLE `profile_views` 279 | MODIFY `view_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=113; 280 | -- 281 | -- AUTO_INCREMENT for table `users` 282 | -- 283 | ALTER TABLE `users` 284 | MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=9; 285 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 286 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 287 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 288 | -------------------------------------------------------------------------------- /less-watch-compiler.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "allowedExtensions":[".less"], 3 | "minified": false, 4 | "sourceMap": false, 5 | "watchFolder": "public/styles/src", 6 | "outputFolder": "public/styles/dist" 7 | } -------------------------------------------------------------------------------- /models/db.js: -------------------------------------------------------------------------------- 1 | const 2 | db = require('./mysql'), 3 | util = require('util'), 4 | bcrypt = require('bcrypt-nodejs') 5 | 6 | const query = (q, data) => { 7 | return new Promise((resolve, reject) => { 8 | db.query(q, data, (err, res) => { 9 | err ? reject(err) : resolve(res) 10 | }) 11 | }) 12 | } 13 | 14 | const createUser = user => { 15 | return new Promise((resolve, reject) => { 16 | bcrypt.hash(user.password, null, null, (error, hash) => { 17 | user.password = hash 18 | db.query('INSERT INTO users SET ?', user, (err, res) => { 19 | err ? reject(err) : resolve(res) 20 | }) 21 | }) 22 | }) 23 | } 24 | 25 | const comparePassword = (password, hash) => { 26 | return new Promise((resolve, reject) => { 27 | bcrypt.compare(password, hash, (err, res) => { 28 | err ? reject(err) : resolve(res) 29 | }) 30 | }) 31 | } 32 | 33 | // FUNCTION TO GET ID FROM USERNAME 34 | const getId = username => { 35 | return new Promise((resolve, reject) => { 36 | query('SELECT id FROM users WHERE username=? LIMIT 1', [username]) 37 | .then(s => resolve(s[0].id)) 38 | .catch(e => reject(e)) 39 | }) 40 | } 41 | 42 | const is_following = (session, user) => { 43 | return new Promise((resolve, reject) => { 44 | query('SELECT COUNT(follow_id) AS is_following FROM follow_system WHERE follow_by=? AND follow_to=? LIMIT 1', [session, user]) 45 | .then(is => resolve((is[0].is_following == 1) ? true : false)) 46 | .catch(e => reject(e)) 47 | }) 48 | } 49 | 50 | module.exports = { 51 | query, 52 | createUser, 53 | comparePassword, 54 | getId, 55 | is_following 56 | } 57 | -------------------------------------------------------------------------------- /models/mail.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer') 2 | 3 | let transporter = nodemailer.createTransport({ 4 | service: "gmail", 5 | auth: { 6 | user: process.env.MAIL, 7 | pass: process.env.MAIL_PASSWORD 8 | } 9 | }) 10 | 11 | let mail = options => { 12 | return new Promise((resolve, reject) => { 13 | let o = Object.assign({}, { 14 | from: `"Notes App" <${process.env.MAIL}>` 15 | }, options) 16 | transporter.sendMail(o, (err, res) => err ? reject(err) : resolve('Mail sent!!')) 17 | }) 18 | } 19 | 20 | module.exports = mail 21 | -------------------------------------------------------------------------------- /models/middlewares.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | const P = require('bluebird') 3 | 4 | const LoggedIn = (req, res, next) => { 5 | !req.session.id ? res.redirect('/login') : next() 6 | } 7 | 8 | const NotLoggedIn = (req, res, next) => { 9 | req.session.id ? res.redirect('/') : next() 10 | } 11 | 12 | const MainRedirect = (req, res, next) => { 13 | req.session.id ? next() : res.redirect('/welcome') 14 | } 15 | 16 | const variables = (req, res, next) => { 17 | let loggedIn = (req.session.id) ? true : false 18 | res.locals.loggedIn = loggedIn 19 | res.locals.session = req.session 20 | next() 21 | } 22 | 23 | const not_found = (req, res, next) => { 24 | let options = { 25 | title: "Oops!" 26 | } 27 | res.status(404).render('error', { options }) 28 | } 29 | 30 | const MeOrNot = (req, res, next) => { 31 | db.query('SELECT COUNT(id) as e FROM users WHERE id=?', [req.params.id]) 32 | .then(is => is[0].e == 0 ? res.redirect('/error') : next()) 33 | .catch(err => console.log(err)) 34 | } 35 | 36 | module.exports = { 37 | LoggedIn, 38 | NotLoggedIn, 39 | MainRedirect, 40 | variables, 41 | not_found, 42 | MeOrNot, 43 | } 44 | -------------------------------------------------------------------------------- /models/mysql.js: -------------------------------------------------------------------------------- 1 | const 2 | mysql = require('mysql'), 3 | hl = require('handy-log'), 4 | { MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE } = process.env 5 | 6 | const db = mysql.createConnection({ 7 | host: MYSQL_HOST, 8 | user: MYSQL_USER, 9 | password: MYSQL_PASSWORD, 10 | database: MYSQL_DATABASE, 11 | charset: "utf8mb4" 12 | }) 13 | 14 | db.connect(err => { 15 | if(err){ 16 | hl.error(err) 17 | } 18 | }) 19 | 20 | module.exports = db 21 | -------------------------------------------------------------------------------- /models/userFn.js: -------------------------------------------------------------------------------- 1 | const 2 | db = require('../models/db'), 3 | mail = require('../models/mail'), 4 | hl = require('handy-log'), 5 | P = require('bluebird'), 6 | fs = require('fs'), 7 | { promisify } = require('util'), 8 | path = require('path'), 9 | dir = process.cwd() 10 | 11 | const signup = (req, res) => { 12 | let { 13 | body: { username, email, password, password_again }, 14 | session 15 | } = req 16 | 17 | req.checkBody('username', 'Username is empty').notEmpty() 18 | req.checkBody('username', 'Username must contain only leters').isAlpha() 19 | req.checkBody('username', 'Username must be greater than 4').isLength({ min: 4 }) 20 | req.checkBody('username', 'Username must be less than 32').isLength({ max: 32 }) 21 | 22 | req.checkBody('email', 'Email is empty').notEmpty() 23 | req.checkBody('email', 'Email is invalid').isEmail() 24 | 25 | req.checkBody('password', 'Password field is empty').notEmpty() 26 | req.checkBody('password_again', 'Password field is empty').notEmpty() 27 | req.checkBody('password', 'Passwords don\'t match').equals(password_again) 28 | 29 | P.coroutine(function *(){ 30 | 31 | let errors = yield req.getValidationResult() 32 | 33 | if(!errors.isEmpty()){ 34 | let array = [] 35 | errors.array().forEach(item => array.push(item.msg) ) 36 | res.json({ mssg: array }) 37 | } else { 38 | 39 | let 40 | [{ usernameCount }] = yield db.query('SELECT COUNT(*) as usernameCount from users WHERE username = ?', [username]), 41 | [{ emailCount }] = yield db.query('SELECT COUNT(*) as emailCount FROM users WHERE email = ?', [email]) 42 | 43 | if(usernameCount == 1){ 44 | res.json({ mssg: "Username already exists!" }) 45 | } else if (emailCount == 1) { 46 | res.json({ mssg: "Email already exists!" }) 47 | } else { 48 | 49 | let 50 | newUser = { 51 | username, 52 | email: req.body.email, 53 | password, 54 | email_verified: "no", 55 | joined: new Date().getTime() 56 | }, 57 | { affectedRows, insertId } = yield db.createUser(newUser) 58 | 59 | if (affectedRows == 1) { 60 | 61 | let mkdir = promisify(fs.mkdir) 62 | yield mkdir(dir + `/public/users/${insertId}`) 63 | fs 64 | .createReadStream(dir + '/public/images/spacecraft.jpg') 65 | .pipe(fs.createWriteStream(dir + `/public/users/${insertId}/user.jpg`)) 66 | 67 | let 68 | url = `http://localhost:${process.env.PORT}/deep/most/topmost/activate/${insertId}`, 69 | options = { 70 | to: email, 71 | subject: "Activate your Notes App account", 72 | html: `Hello, You received this message because you created an account on Notes App.
Click on button below to activate your account and explore.

Activate` 73 | } 74 | 75 | mail(options) 76 | .then(m => { 77 | hl.success(m) 78 | session.id = insertId 79 | session.username = username 80 | session.email_verified = "no" 81 | res.json({ 82 | mssg: `Hello, ${session.username}!!`, 83 | success: true 84 | }) 85 | }) 86 | .catch(me => { 87 | hl.error(me) 88 | res.json({ 89 | mssg: `Hello, ${session.username}. Mail could not be sent!!`, 90 | success: true 91 | }) 92 | }) 93 | 94 | } 95 | 96 | } 97 | 98 | } 99 | 100 | })() 101 | 102 | } 103 | 104 | const login = (req, res) => { 105 | P.coroutine(function* (){ 106 | let { 107 | body: { username: rusername, password: rpassword }, 108 | session 109 | } = req 110 | 111 | req.checkBody('username', 'Username is empty').notEmpty() 112 | req.checkBody('password', 'Password field is empty').notEmpty() 113 | 114 | let errors = yield req.getValidationResult() 115 | 116 | if(!errors.isEmpty()){ 117 | let array = [] 118 | errors.array().forEach(item => array.push(item.msg) ) 119 | res.json({ mssg: array }) 120 | } else { 121 | 122 | let [{ userCount, id, password, email_verified }] = yield db.query('SELECT COUNT(id) as userCount, id, password, email_verified from users WHERE username = ? LIMIT 1', [rusername]) 123 | 124 | if(userCount == 0){ 125 | res.json({ mssg: "User not found!" }) 126 | } else if(userCount > 0) { 127 | let same = yield db.comparePassword(rpassword, password) 128 | if(!same){ 129 | res.json({ mssg: "Wrong password!" }) 130 | } else { 131 | session.id = id 132 | session.username = rusername 133 | session.email_verified = email_verified 134 | 135 | res.json({ mssg: `Hello, ${session.username}!!`, success: true }) 136 | } 137 | } 138 | 139 | } 140 | 141 | })() 142 | } 143 | 144 | const registered = (req, res) => { 145 | P.coroutine(function *(){ 146 | let 147 | { id } = req.session, 148 | [{ email_verified }] = yield db.query("SELECT email_verified FROM users WHERE id=? LIMIT 1", [id]), 149 | options = { 150 | title: "You are now registered!!", 151 | mssg: "Email has been sent. Check your inbox and click on the provided link!!" 152 | } 153 | 154 | email_verified == "yes" ? 155 | res.redirect('/') 156 | : 157 | res.render("registered", { options }) 158 | 159 | })() 160 | } 161 | 162 | const activate = (req, res) => { 163 | P.coroutine(function *(){ 164 | let 165 | { params: { id }, session } = req, 166 | { changedRows } = yield db.query('UPDATE users SET email_verified=? WHERE id=?', ["yes", id]), 167 | mssg 168 | 169 | session.email_verified = "yes" 170 | mssg = changedRows == 0 ? "alr" : "yes" 171 | 172 | res.redirect(`/email-verification/${mssg}`) 173 | 174 | })() 175 | } 176 | 177 | module.exports = { 178 | signup, 179 | login, 180 | registered, 181 | activate, 182 | } 183 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-mini-social-network", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "nodemon app.js", 8 | "dev": "webpack -d --watch", 9 | "build": "webpack -p --watch", 10 | "less": "less-watch-compiler", 11 | "public": "concurrently \"npm run dev\" \"npm run less\"" 12 | }, 13 | "author": "takkar ", 14 | "license": "ISC", 15 | "dependencies": { 16 | "animate-components": "^1.4.2", 17 | "axios": "^0.16.1", 18 | "bcrypt-nodejs": "^0.0.3", 19 | "bluebird": "^3.5.0", 20 | "body-parser": "^1.17.2", 21 | "client-sessions": "^0.8.0", 22 | "concurrently": "^3.5.0", 23 | "dotenv": "^4.0.0", 24 | "express": "^4.15.2", 25 | "express-handlebars": "^3.0.0", 26 | "express-validator": "^3.2.0", 27 | "handy-copy": "^1.0.6", 28 | "handy-image-processor": "^1.0.1", 29 | "handy-log": "^1.0.2", 30 | "handy-notification": "^1.0.23", 31 | "handy-timeago": "^1.0.1", 32 | "handy-tooltip": "^1.0.10", 33 | "jquery": "^3.2.1", 34 | "morgan": "^1.8.2", 35 | "multer": "^1.3.0", 36 | "mysql": "^2.13.0", 37 | "nodemailer": "^4.0.1", 38 | "prop-types": "^15.5.8", 39 | "react": "^15.5.4", 40 | "react-custom-scrollbars": "^4.1.2", 41 | "react-dom": "^15.5.4", 42 | "react-helmet": "^5.1.3", 43 | "react-redux": "^5.0.4", 44 | "react-router": "^4.1.2", 45 | "react-router-dom": "^4.1.2", 46 | "redux": "^3.6.0", 47 | "redux-logger": "^3.0.1", 48 | "redux-promise-middleware": "4.2.1", 49 | "redux-thunk": "^2.2.0", 50 | "serve-favicon": "^2.4.3" 51 | }, 52 | "devDependencies": { 53 | "babel-core": "^6.24.1", 54 | "babel-loader": "^7.0.0", 55 | "babel-plugin-react-html-attrs": "^2.0.0", 56 | "babel-plugin-transform-class-properties": "^6.24.1", 57 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 58 | "babel-plugin-transform-react-jsx-source": "^6.22.0", 59 | "babel-plugin-transform-runtime": "^6.23.0", 60 | "babel-preset-env": "^1.4.0", 61 | "babel-preset-es2015": "^6.24.1", 62 | "babel-preset-react": "^6.24.1", 63 | "babel-preset-stage-0": "^6.24.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /public/images/favicon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetstar82/React-SocialNetwork/a987ccb9cc907b14cbee6a0115fccbcf2d92d704/public/images/favicon/favicon.png -------------------------------------------------------------------------------- /public/images/images (11).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetstar82/React-SocialNetwork/a987ccb9cc907b14cbee6a0115fccbcf2d92d704/public/images/images (11).png -------------------------------------------------------------------------------- /public/images/large.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetstar82/React-SocialNetwork/a987ccb9cc907b14cbee6a0115fccbcf2d92d704/public/images/large.jpg -------------------------------------------------------------------------------- /public/images/spacecraft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetstar82/React-SocialNetwork/a987ccb9cc907b14cbee6a0115fccbcf2d92d704/public/images/spacecraft.jpg -------------------------------------------------------------------------------- /public/images/tumblr_nk2y2oVEjp1rn9vmdo1_500.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dotnetstar82/React-SocialNetwork/a987ccb9cc907b14cbee6a0115fccbcf2d92d704/public/images/tumblr_nk2y2oVEjp1rn9vmdo1_500.gif -------------------------------------------------------------------------------- /public/js/src/actions/explore-action.js: -------------------------------------------------------------------------------- 1 | import { post } from 'axios' 2 | 3 | const get_explores = () => { 4 | return dispatch => { 5 | post('/api/explore') 6 | .then(exp => dispatch({ type: "GET_EXPLORES", payload: exp.data }) ) 7 | .catch(err => console.log(err) ) 8 | } 9 | } 10 | 11 | module.exports = { 12 | get_explores 13 | } 14 | -------------------------------------------------------------------------------- /public/js/src/actions/follow-action.js: -------------------------------------------------------------------------------- 1 | import { post } from 'axios' 2 | 3 | const get_profile_views = username => { 4 | return dispatch => { 5 | post('/api/get-profile-views', { username }) 6 | .then(view => dispatch({ type: "GET_PROFILE_VIEWS", payload: view.data }) ) 7 | .catch(err => console.log(err) ) 8 | } 9 | } 10 | 11 | const is_following = username => { 12 | return dispatch => { 13 | post('/api/is-following', { username }) 14 | .then(is => dispatch({ type: "IS_FOLLOWING", payload: is.data }) ) 15 | .catch(err => console.log(err) ) 16 | } 17 | } 18 | 19 | const get_followers = username => { 20 | return dispatch => { 21 | post('/api/get-followers', { username }) 22 | .then(followers => dispatch({ type: "GET_FOLLOWERS", payload: followers.data }) ) 23 | .catch(err => console.log(err) ) 24 | } 25 | } 26 | 27 | const get_followings = username => { 28 | return dispatch => { 29 | post('/api/get-followings', { username }) 30 | .then(following => dispatch({ type: "GET_FOLLOWINGS", payload: following.data }) ) 31 | .catch(err => console.log(err) ) 32 | } 33 | } 34 | 35 | const follower = follower => { 36 | return { 37 | type: "FOLLOWER", 38 | payload: follower 39 | } 40 | } 41 | 42 | const unfollower = unfollower => { 43 | return{ 44 | type: "UNFOLLOWER", 45 | payload: unfollower 46 | } 47 | } 48 | 49 | const following = following => { 50 | return{ 51 | type: "FOLLOWING", 52 | payload: following 53 | } 54 | } 55 | 56 | const unfollowing = unfollowing => { 57 | return { 58 | type: "UNFOLLOWING", 59 | payload: unfollowing 60 | } 61 | } 62 | 63 | module.exports = { 64 | get_profile_views, 65 | is_following, 66 | get_followers, 67 | get_followings, 68 | follower, 69 | unfollower, 70 | following, 71 | unfollowing 72 | } 73 | -------------------------------------------------------------------------------- /public/js/src/actions/note-int-action.js: -------------------------------------------------------------------------------- 1 | import { post } from 'axios' 2 | 3 | const note_details = note => { 4 | return dispatch => { 5 | post('/api/get-note-details', { note }) 6 | .then(s => dispatch({ type: "NOTE_DETAILS", payload: s.data }) ) 7 | .catch(e => console.log(err) ) 8 | } 9 | } 10 | 11 | const likes = note => { 12 | return dispatch => { 13 | post('/api/likes', { note }) 14 | .then(likes => dispatch({ type: "LIKES", payload: likes.data }) ) 15 | .catch(err => console.log(err) ) 16 | } 17 | } 18 | 19 | const liked = obj => { 20 | return { 21 | type: "LIKED", 22 | payload: obj 23 | } 24 | } 25 | 26 | const unliked = note => { 27 | return { 28 | type: "UNLIKED", 29 | payload: note 30 | } 31 | } 32 | 33 | module.exports = { 34 | note_details, 35 | likes, 36 | liked, 37 | unliked 38 | } 39 | -------------------------------------------------------------------------------- /public/js/src/actions/notes-action.js: -------------------------------------------------------------------------------- 1 | import { post } from 'axios' 2 | 3 | const getNotes = get => { 4 | return dispatch => { 5 | post('/api/get-notes', { get }) 6 | .then(notes => dispatch({ type: "GET_NOTES", payload: notes.data }) ) 7 | .catch(err => dispatch({ type: "GET_NOTES_ERR", payload: err }) ) 8 | } 9 | } 10 | 11 | const updateNote = note => { 12 | return { 13 | type: "UPDATE_NOTES", 14 | payload: note 15 | } 16 | } 17 | 18 | const deleteNote = note => { 19 | return { 20 | type: "DELETE_NOTE", 21 | payload: note 22 | } 23 | } 24 | 25 | const editNote = note_details => { 26 | return { 27 | type: "EDIT_NOTE", 28 | payload: note_details 29 | } 30 | } 31 | 32 | const getFeeds = () => { 33 | return dispatch => { 34 | post('/api/feeds') 35 | .then(notes => dispatch({ type: "GET_FEEDS", payload: notes.data }) ) 36 | .catch(err => console.log(err) ) 37 | } 38 | } 39 | 40 | module.exports = { 41 | getNotes, 42 | updateNote, 43 | deleteNote, 44 | editNote, 45 | getFeeds 46 | } 47 | -------------------------------------------------------------------------------- /public/js/src/actions/user-action.js: -------------------------------------------------------------------------------- 1 | import { post } from 'axios' 2 | 3 | const user_details = get => { 4 | return dispatch => { 5 | post('/api/get-details', { get }) 6 | .then(get => dispatch({type: "USER_DETAILS", payload: get.data }) ) 7 | .catch(err => console.log(err) ) 8 | } 9 | } 10 | 11 | module.exports = { 12 | user_details 13 | } 14 | -------------------------------------------------------------------------------- /public/js/src/components/app-comp.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react' 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' 3 | 4 | import Header from './others/header-comp' 5 | import Profile from './profile/profile-comp' 6 | import Home from './home/home-comp' 7 | import Edit from './edit/edit-comp' 8 | import Explore from './explore/explore-comp' 9 | import Error from './error/error-comp' 10 | import EmailVerification from './email-verification/email-ver-comp' 11 | import Deactivate from './deactivate/deactivate-comp' 12 | import Viewnote from './note/view-note-comp' 13 | import Overlay from './others/overlay-comp' 14 | 15 | export default class App extends Component{ 16 | render(){ 17 | return( 18 | 19 |
20 |
21 |
22 | } /> 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 |
36 |
37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/js/src/components/deactivate/deactivate-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import axios from 'axios' 4 | import { FadeIn } from 'animate-components' 5 | import Title from '../others/title-comp' 6 | import * as fn from '../../functions/functions' 7 | 8 | import Overlay from '../others/overlay-comp' 9 | import Prompt from '../others/prompt-comp' 10 | 11 | export default class Deactivate extends React.Component{ 12 | 13 | state = { deactivate: false } 14 | 15 | toggle_ = (e, what) => { 16 | e ? e.preventDefault() : null 17 | switch (what) { 18 | case "deactivate": 19 | this.setState(state => ({ deactivate: !state.deactivate })) 20 | break 21 | } 22 | } 23 | 24 | deactivate = e => { 25 | e.preventDefault() 26 | fn.deactivate() 27 | } 28 | 29 | render(){ 30 | let { deactivate } = this.state 31 | 32 | return ( 33 |
34 | 35 | <FadeIn duration="300ms" > 36 | <div class="registered deactivate" > 37 | <span className="deactivate_title" >Deactivate your account?</span> 38 | <span>All of your notes, followers, followings & info will be permanently deleted. And you won't be able to find it again.</span> 39 | <div className="deactivate_btn"> 40 | <a href="#" className="pri_btn d_btn" onClick={e => this.toggle_(e, "deactivate")} >Deactivate</a> 41 | </div> 42 | </div> 43 | </FadeIn> 44 | 45 | {deactivate ? <Overlay /> : null} 46 | { 47 | deactivate ? 48 | <Prompt 49 | title="Deactivate your account" 50 | content="Are you sure, you wanna permanently deactivate your account? There's no undo so you won't be able login with this account." 51 | actionText="Deactivate" 52 | action={this.deactivate} 53 | state_updater="deactivate" 54 | close={this.toggle_} 55 | /> 56 | : null 57 | } 58 | 59 | </div> 60 | ) 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /public/js/src/components/edit/edit-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import axios from 'axios' 4 | import { connect } from 'react-redux' 5 | import Title from '../others/title-comp' 6 | import { FadeIn } from 'animate-components' 7 | import Notify from 'handy-notification' 8 | import TimeAgo from 'handy-timeago' 9 | import P from 'bluebird' 10 | import * as fn from '../../functions/functions' 11 | import * as user_action from '../../actions/user-action' 12 | 13 | @connect(store => { 14 | return { 15 | user: store.user 16 | } 17 | }) 18 | 19 | export default class Edit extends React.Component{ 20 | 21 | state = { 22 | username: "", 23 | email: "", 24 | bio: "", 25 | file: "" 26 | } 27 | 28 | componentDidMount = () => { 29 | let 30 | { dispatch } = this.props, 31 | username = $('.data').data('username') 32 | dispatch(user_action.user_details(username)) 33 | } 34 | 35 | componentWillReceiveProps = ({ user: { user_details: { username, email, bio } }}) => 36 | this.setState({ username, email, bio }) 37 | 38 | update_ = (e, what) => { 39 | this.setState({ [what]: e.target.value }) 40 | } 41 | 42 | edit_profile = e => { 43 | e.preventDefault() 44 | let 45 | { username: susername, email: semail } = this.props.user.user_details, 46 | { username, email, bio } = this.state 47 | fn.edit_profile({ susername, semail, username, email, bio }) 48 | } 49 | 50 | change_avatar = e => { 51 | this.update_(e, "file") 52 | fn.change_avatar({ file: e.target.files[0] }) 53 | } 54 | 55 | resend_vl = e => { 56 | e.preventDefault() 57 | fn.resend_vl() 58 | } 59 | 60 | render(){ 61 | let 62 | { username, email, bio, file } = this.state, 63 | { id, joined } = this.props.user.user_details 64 | 65 | return( 66 | <div class='edit'> 67 | 68 | <Title value="Edit profile" /> 69 | 70 | <FadeIn duration="300ms" className="edit_animation" > 71 | <div class="edit_info"> 72 | <img 73 | className="edit_img" 74 | src={id ? `/users/${id}/user.jpg` : "/images/spacecraft.jpg" } 75 | alt="Your avatar" 76 | /> 77 | <span>{`@${username}`}</span> 78 | </div> 79 | <div className="eu_div"> 80 | <span class='edit_span'>Username</span> 81 | <input 82 | type="text" 83 | class='e_username' 84 | placeholder='Username..' 85 | autoComplete='false' 86 | autoFocus 87 | spellCheck='false' 88 | value={username} 89 | onChange={e => this.update_(e, "username")} 90 | /> 91 | </div> 92 | <div className="ee_div"> 93 | <span class='edit_span'>Email</span> 94 | <input 95 | type="email" 96 | class='e_email' 97 | placeholder='Email..' 98 | autoComplete='false' 99 | spellCheck='false' 100 | value={email} 101 | onChange={e => this.update_(e, "email")} 102 | /> 103 | </div> 104 | <div className="eb_div"> 105 | <span class='edit_span'>Bio</span> 106 | <textarea 107 | class="e_bio" 108 | placeholder='Bio..' 109 | spellCheck='false' 110 | value={bio} 111 | onChange={e => this.update_(e, "bio")} 112 | ></textarea> 113 | </div> 114 | <div className="eb_btns"> 115 | <form class='avatar_form' method="post" encType='multipart/formdata' > 116 | <input 117 | type="file" 118 | name="avatar" 119 | id="avatar_file" 120 | accept="image/*" 121 | value={file} 122 | onChange={this.change_avatar} 123 | /> 124 | <label 125 | for="avatar_file" 126 | class={`avatar_span sec_btn ${!fn.e_v() ? "sec_btn_disabled" : ""}`} 127 | >{fn.e_v() ? "Change avatar" : "Verify email to change avatar"}</label> 128 | </form> 129 | <a href="#" className="pri_btn e_done" onClick={this.edit_profile} >Done editing</a> 130 | </div> 131 | <div className="e_joined"> 132 | <span>{`You joined Notes App ${TimeAgo(joined)}`}</span> 133 | </div> 134 | 135 | { 136 | !fn.e_v() ? 137 | <div className="resend_vl_div" > 138 | <a href='#' className="pri_btn resend_vl" onClick={this.resend_vl} >Resend verification link</a> 139 | </div> 140 | : null 141 | } 142 | 143 | </FadeIn> 144 | 145 | </div> 146 | ) 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /public/js/src/components/email-verification/email-ver-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Title from '../others/title-comp' 4 | import { FadeIn } from 'animate-components' 5 | 6 | export default class EmailVerification extends React.Component{ 7 | render(){ 8 | let 9 | { params: { is } } = this.props.match, 10 | mssg 11 | 12 | if(is == "yes"){ 13 | mssg = "You email has been verified successfully!" 14 | } else if(is == "alr"){ 15 | mssg = "Email already verified!" 16 | } else { 17 | mssg = "Something went wrong!" 18 | } 19 | 20 | return( 21 | <div> 22 | <Title value="E-mail verification"/> 23 | <FadeIn duration="300ms" > 24 | <div class="registered"> 25 | <span>{mssg}</span> 26 | </div> 27 | </FadeIn> 28 | </div> 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/js/src/components/error/error-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import { Link } from 'react-router-dom' 4 | import Title from '../others/title-comp' 5 | import { FadeIn } from 'animate-components' 6 | 7 | export default class Error extends React.Component{ 8 | render(){ 9 | let 10 | username = $('.data').data('username'), 11 | { params: { what } } = this.props.match, 12 | title, 13 | desc 14 | 15 | if(what == "notfound"){ 16 | title = "User not found" 17 | desc = "user" 18 | } else if(what == "note_notfound"){ 19 | title = "Note not found" 20 | desc = "note" 21 | } else { 22 | title = "Error" 23 | desc = "page" 24 | } 25 | 26 | return( 27 | <div class='error' > 28 | <Title value="Oops! {title}" /> 29 | <FadeIn duration="300ms" > 30 | <div className="welcome_div error_div"> 31 | <div className="error_info"> 32 | <span>Oops, the {desc} you're looking for does not exist!!</span> 33 | </div> 34 | <img src="/images/error-3.svg" alt="" /> 35 | <div class="error_bottom"> 36 | <Link to={`/profile/${username}`} className="sec_btn error_home" >View profile</Link> 37 | <Link to='/' className="pri_btn error_login" >Try going to homepage</Link> 38 | </div> 39 | </div> 40 | </FadeIn> 41 | </div> 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/js/src/components/explore/explore-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import Title from '../others/title-comp' 4 | import { FadeIn } from 'animate-components' 5 | import { get_explores } from '../../actions/explore-action' 6 | import Explores_list from './explores-list' 7 | import Nothing from '../others/nothing-comp' 8 | import End from '../others/end-comp' 9 | 10 | @connect(store => { 11 | return { 12 | explore: store.explore.explores 13 | } 14 | }) 15 | 16 | export default class Explore extends React.Component { 17 | 18 | componentDidMount = () => this.props.dispatch(get_explores()) 19 | 20 | render(){ 21 | let 22 | { explore } = this.props, 23 | map_explore = explore.map(e => 24 | <Explores_list key={e.id} {...e} /> 25 | ) 26 | 27 | return( 28 | <div className="explore" > 29 | 30 | <Title value="Explore" /> 31 | 32 | <FadeIn duration="300ms" > 33 | <div className="explores" > 34 | {explore.length == 0 ? <Nothing mssg="No one to explore!!" /> : map_explore} 35 | {explore.length != 0 ? <End /> : null} 36 | </div> 37 | </FadeIn> 38 | 39 | </div> 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /public/js/src/components/explore/explores-list.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import axios from 'axios' 3 | import { Link } from 'react-router-dom' 4 | import { connect } from 'react-redux' 5 | import * as fn from '../../functions/functions' 6 | 7 | export default class Explores_list extends React.Component{ 8 | 9 | state = { 10 | no_of_notes: 0, 11 | is_following: false 12 | } 13 | 14 | componentDidMount = async () => { 15 | let 16 | { dispatch, id, username } = this.props, 17 | { data: no_of_notes } = await axios.post('/api/no-of-notes', { user: id }), 18 | { data: is_following } = await axios.post('/api/is-following', { username }) 19 | this.setState({ no_of_notes, is_following }) 20 | } 21 | 22 | follow = e => { 23 | e.preventDefault() 24 | let 25 | { id, username } = this.props, 26 | obj = { 27 | user: id, 28 | username, 29 | done: () => this.setState({ is_following: true }) 30 | } 31 | fn.follow(obj) 32 | } 33 | 34 | unfollow = e => { 35 | e.preventDefault() 36 | let 37 | { id, username } = this.props, 38 | obj = { 39 | user: id, 40 | done: () => this.setState({ is_following: false }) 41 | } 42 | fn.unfollow(obj) 43 | } 44 | 45 | render(){ 46 | let 47 | { id, username, email } = this.props, 48 | { no_of_notes, is_following } = this.state, 49 | n = no_of_notes == 0 ? '0 notes' : no_of_notes == 1 ? '1 note' : `${no_of_notes} notes` 50 | 51 | return( 52 | <div className="explores_list" > 53 | <div className="exl_main"> 54 | <img src={id ? `/users/${id}/user.jpg` : '/images/spacecraft.jpg'} /> 55 | <div className="exl_content"> 56 | <Link to={`/profile/${username}`} className="exl_username" >{username}</Link> 57 | <div className="exl_desc"> 58 | <span className="exl_email">{email}</span> 59 | <span className="exl_desc_sep">•</span> 60 | <span className="exl_followers">{n}</span> 61 | </div> 62 | </div> 63 | </div> 64 | <div className="exl_ff"> 65 | { 66 | is_following ? 67 | <a href="#" className="pri_btn unfollow exl_unfollow" onClick={this.unfollow} >Followed</a> 68 | : 69 | <a href="#" className="pri_btn follow exl_follow" onClick={this.follow} >Follow</a> 70 | } 71 | </div> 72 | </div> 73 | ) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /public/js/src/components/home/feeds-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import { connect } from 'react-redux' 4 | 5 | import Nothing from '../others/nothing-comp' 6 | import End from '../others/end-comp' 7 | import Note from '../note/note-comp' 8 | import * as fn from '../../functions/functions' 9 | 10 | @connect(store => { 11 | return { 12 | notes: store.notes 13 | } 14 | }) 15 | 16 | export default class Feeds extends React.Component{ 17 | render(){ 18 | let 19 | { notes: { feeds } } = this.props, 20 | map_feeds = feeds.map(feed => 21 | <Note key={feed.note_id} {...feed} /> 22 | ) 23 | 24 | return( 25 | <div class='feeds_wrapper' > 26 | { feeds.length == 0 ? <Nothing mssg="No feeds available!" /> : map_feeds } 27 | { feeds.length != 0 ? <End /> : null } 28 | </div> 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/js/src/components/home/home-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import Title from '../others/title-comp' 4 | import { FadeIn } from 'animate-components' 5 | import { connect } from 'react-redux' 6 | import { Link } from 'react-router-dom' 7 | 8 | import Feeds from './feeds-comp' 9 | import * as fn from '../../functions/functions' 10 | import * as note_action from '../../actions/notes-action' 11 | 12 | @connect(store => { 13 | return { 14 | notes: store.notes 15 | } 16 | }) 17 | 18 | export default class Home extends React.Component{ 19 | 20 | componentDidMount = () => 21 | this.props.dispatch(note_action.getFeeds()) 22 | 23 | render(){ 24 | let 25 | s_username = $('.data').data('username'), 26 | { notes: { feeds } } = this.props, 27 | no_of_feeds = feeds.length == 0 ? "No feeds" : feeds.length == 1 ? '1 feed' : `${feeds.length} notes` 28 | 29 | return( 30 | <div class='home' > 31 | <Title value="Home" /> 32 | <FadeIn duration="300ms" > 33 | <div className="home_info"> 34 | <span>{no_of_feeds}</span> 35 | <Link 36 | to={{ pathname: `/profile/${s_username}/create-note` }} 37 | class={`pri_btn ${!fn.e_v() ? "a_disabled" : ""}`} 38 | >{fn.e_v() ? "Create note" : "Verify email to create note"} 39 | </Link> 40 | </div> 41 | <Feeds /> 42 | </FadeIn> 43 | </div> 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /public/js/src/components/note/create-note-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import { FadeIn } from 'animate-components' 4 | import Title from '../others/title-comp' 5 | import { connect } from 'react-redux' 6 | import * as fn from '../../functions/functions' 7 | import Goto from '../others/goto-comp' 8 | 9 | @connect(store => { 10 | return { 11 | notes: store.notes 12 | } 13 | }) 14 | 15 | export default class Create_note extends React.Component{ 16 | 17 | back = e => fn.back(e, this.props.history) 18 | 19 | addNote = e => { 20 | e.preventDefault() 21 | let 22 | title = $('.c_n_middle input[type="text"]').val(), 23 | content = $('.c_n_middle textarea').val(), 24 | { dispatch, history } = this.props 25 | fn.createNote({ title, content, dispatch, history }) 26 | } 27 | 28 | render(){ 29 | return ( 30 | <div class='create_note modal'> 31 | <Title value="Create note" /> 32 | <FadeIn duration="300ms" > 33 | <form onSubmit={this.addNote} > 34 | <div className="c_n_header modal_header"> 35 | <span className="title" >Create a note</span> 36 | <Goto /> 37 | </div> 38 | <div className="c_n_middle modal_middle"> 39 | <input type="text" placeholder='Title..' required spellCheck="false" autoComplete="false" autoFocus /> 40 | <textarea placeholder='Your note..' required spellCheck='false' autoComplete='false' ></textarea> 41 | </div> 42 | <div className="c_n_bottom modal_bottom"> 43 | <a href="#" className='c_n_cancel sec_btn' onClick={this.back} >Back</a> 44 | <input type="submit" className='c_n_add pri_btn' value='Add note' /> 45 | </div> 46 | </form> 47 | </FadeIn> 48 | </div> 49 | ) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /public/js/src/components/note/like-items.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import axios from 'axios' 3 | import $ from 'jquery' 4 | import TimeAgo from 'handy-timeago' 5 | import { Link } from 'react-router-dom' 6 | import { connect } from 'react-redux' 7 | import * as fn from '../../functions/functions' 8 | 9 | @connect(store => { 10 | return {} 11 | }) 12 | 13 | export default class Like_items extends React.Component{ 14 | 15 | state = { is_following: false } 16 | 17 | componentDidMount = async () => { 18 | let { like_by, like_by_username: username } = this.props 19 | if(!fn.Me(like_by)) { 20 | let { data } = await axios.post('/api/is-following', { username }) 21 | this.setState({ is_following: data }) 22 | } 23 | } 24 | 25 | follow = e => { 26 | e.preventDefault() 27 | let 28 | { like_by, like_by_username, dispatch } = this.props, 29 | getid = $('.profile_data').data('getid'), 30 | obj = { 31 | user: like_by, 32 | username: like_by_username, 33 | done: () => this.setState({ is_following: true }) 34 | } 35 | fn.follow(obj) 36 | } 37 | 38 | unfollow = e => { 39 | e.preventDefault() 40 | let 41 | { like_by, dispatch } = this.props, 42 | getid = $('.profile_data').data('getid'), 43 | obj = { 44 | user: like_by, 45 | done: () => this.setState({ is_following: false }) 46 | } 47 | fn.unfollow(obj) 48 | } 49 | 50 | render(){ 51 | let 52 | { like_by, like_by_username, like_time } = this.props, 53 | { is_following } = this.state 54 | 55 | return( 56 | <div className="modal_items fer_items" > 57 | <div className="modal_it_img"> 58 | <img src={ like_by ? `/users/${like_by}/user.jpg` : `/images/spacecraft.jpg`} /> 59 | </div> 60 | <div className="modal_it_content"> 61 | <div className="modal_it_info"> 62 | <Link to={`/profile/${like_by_username}`} class='modal_it_username' >{like_by_username}</Link> 63 | <span class='modal_it_light' >{TimeAgo(like_time)}</span> 64 | </div> 65 | <div className="modal_ff"> 66 | { 67 | fn.Me(like_by) ? 68 | <Link to={`/profile/${like_by}`} class='pri_btn follow' >Profile</Link> 69 | : is_following ? 70 | <a href="#" class='pri_btn unfollow' onClick={this.unfollow} >Unfollow</a> 71 | : 72 | <a href="#" class='pri_btn follow' onClick={this.follow} >Follow</a> 73 | } 74 | </div> 75 | </div> 76 | <hr /> 77 | </div> 78 | ) 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /public/js/src/components/note/likes-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { connect } from 'react-redux' 3 | import Title from '../others/title-comp' 4 | import { FadeIn } from 'animate-components' 5 | import { Scrollbars } from 'react-custom-scrollbars' 6 | import * as fn from '../../functions/functions' 7 | 8 | import Goto from '../others/goto-comp' 9 | import Nothing from '../others/nothing-comp' 10 | import Like_items from './like-items' 11 | 12 | @connect(store => { 13 | return { 14 | note_int: store.note_int 15 | } 16 | }) 17 | 18 | export default class Likes extends React.Component{ 19 | 20 | back = e => fn.back(e, this.props.history) 21 | 22 | componentDidMount = () => fn.last_line_remover() 23 | componentDidUpdate = () => fn.last_line_remover() 24 | 25 | render(){ 26 | let 27 | { note_int: { likes, note_details: { note_id } } } = this.props, 28 | map_l = likes.map(l => 29 | <Like_items key={l.like_id} {...l} /> 30 | ) 31 | 32 | return( 33 | <div class='likes modal modal_big' > 34 | <Title value="Likes" /> 35 | <FadeIn duration="300ms" > 36 | <div className="likes_header modal_header"> 37 | <span className="title" >Likes</span> 38 | <Goto /> 39 | </div> 40 | <Scrollbars style={{ height: 450 }} className="likes_middle modal_middle"> 41 | <div className="modal_main"> 42 | { likes.length == 0 ? <Nothing showMssg={false} /> : map_l } 43 | </div> 44 | </Scrollbars> 45 | <div className="likes_bottom modal_bottom"> 46 | <a href='#' className='likes_cancel pri_btn' onClick={this.back} >Back</a> 47 | </div> 48 | </FadeIn> 49 | </div> 50 | ) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/js/src/components/note/note-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import * as fn from '../../functions/functions' 4 | import TimeAgo from 'handy-timeago' 5 | import { Link } from 'react-router-dom' 6 | 7 | export default class Note extends React.Component{ 8 | render(){ 9 | let { title, content, note_id, user, username, note_time } = this.props 10 | 11 | return( 12 | <Link to={{ pathname:`/view-note/${note_id}`, state: { modal: true } }}> 13 | <div class='note' data-note={note_id} > 14 | <div className="note_header common_header"> 15 | <img src={ user ? `/users/${user}/user.jpg` : '/images/spacecraft.jpg' } alt=""/> 16 | <div className="note_h_left"> 17 | <span className="note_username">{username}</span> 18 | <span className='note_time' >{TimeAgo(note_time)}</span> 19 | </div> 20 | </div> 21 | <div className="note_title"> 22 | <span>{fn.c_first(title)}</span> 23 | </div> 24 | <div className="note_content"> 25 | <span>{fn.shortener(fn.c_first(content), 500)}</span> 26 | </div> 27 | </div> 28 | </Link> 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /public/js/src/components/note/view-note-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import axios from 'axios' 4 | import { connect } from 'react-redux' 5 | import Title from '../others/title-comp' 6 | import { FadeIn } from 'animate-components' 7 | import { Link, Route, Redirect } from 'react-router-dom' 8 | import Notify from 'handy-notification' 9 | import Tooltip from 'handy-tooltip' 10 | import Timeago from 'handy-timeago' 11 | 12 | import Goto from '../others/goto-comp' 13 | import Overlay from '../others/overlay-comp' 14 | import Prompt from '../others/prompt-comp' 15 | import Likes from '../note/likes-comp' 16 | 17 | import * as fn from '../../functions/functions' 18 | import * as note_int_action from '../../actions/note-int-action' 19 | 20 | @connect(store => { 21 | return { 22 | note_int: store.note_int 23 | } 24 | }) 25 | 26 | export default class View_note extends React.Component{ 27 | 28 | state = { 29 | deleting: false, 30 | editing: false, 31 | liked: false, 32 | invalid_note: false 33 | } 34 | 35 | componentDidMount = async () => { 36 | let { match: { params: { note } }, dispatch } = this.props 37 | let { data } = await axios.post('/api/liked-or-not', { note }) 38 | this.setState({ liked: data }) 39 | dispatch(note_int_action.note_details(note)) 40 | dispatch(note_int_action.likes(note)) 41 | } 42 | 43 | componentWillReceiveProps = ({ note_int: { note_details: { note_id } } }) => 44 | !note_id ? this.setState({ invalid_note: true }) : null 45 | 46 | toggle_ = (e, what) => { 47 | e ? e.preventDefault() : null 48 | this.setState({ [what]: !this.state[what] }) 49 | what == 'editing' ? $('.v_n_edit').blur() : null 50 | } 51 | 52 | back = e => fn.back(e, this.props.history) 53 | 54 | delete = e => { 55 | e.preventDefault() 56 | let { dispatch, history, match: { params: { note } } } = this.props 57 | fn.deleteNote({ note, dispatch, history }) 58 | } 59 | 60 | edit = e => { 61 | this.toggle_(e, "editing") 62 | let 63 | title = $('.v_n_title').text(), 64 | content = $('.v_n_content').text(), 65 | { dispatch, note_int: { note_details: { note_id } } } = this.props 66 | fn.editNote({ title, content, note_id, setState: this.setState, dispatch }) 67 | } 68 | 69 | like = e => { 70 | let 71 | { dispatch, match: { params: { note } } } = this.props, 72 | options = { note, dispatch, done: () => this.setState({ liked: true }) } 73 | fn.like(options) 74 | } 75 | 76 | unlike = e => { 77 | let 78 | { dispatch, match: { params: { note } } } = this.props, 79 | options = { note, dispatch, done: () => this.setState({ liked: false }) } 80 | fn.unlike(options) 81 | } 82 | 83 | render(){ 84 | let 85 | { deleting, editing, liked, invalid_note } = this.state, 86 | { note_int: { likes, note_details: { user, username, title, content, note_time } }, match } = this.props 87 | 88 | Tooltip({ 89 | selector: $('.like_unlike'), 90 | value: liked ? "Unlike" : "Like" 91 | }) 92 | 93 | return ( 94 | <div class='view_note modal'> 95 | 96 | { invalid_note ? <Redirect to="/error/note_notfound" /> : null } 97 | 98 | <Title value="View note" /> 99 | 100 | <FadeIn duration="300ms" > 101 | <div className="v_n_header modal_header"> 102 | <span class='title' >View note</span> 103 | <Goto /> 104 | </div> 105 | <div className="v_n_middle modal_middle"> 106 | <div className="v_n_info"> 107 | <img src={user ? `/users/${user}/user.jpg` : '/images/spacecraft.jpg'} alt="" /> 108 | <div className="v_n_left"> 109 | <Link to={`/profile/${username}`} className='v_n_username' >{username}</Link> 110 | <span className="v_n_time">{Timeago(note_time)}</span> 111 | </div> 112 | </div> 113 | <span 114 | className='v_n_title' 115 | contentEditable={editing} 116 | spellCheck='false' 117 | suppressContentEditableWarning={true} 118 | >{title}</span> 119 | <span 120 | className={`v_n_content ${editing ? 'content_editor' : ''} `} 121 | contentEditable={editing} 122 | spellCheck='false' 123 | suppressContentEditableWarning={true} 124 | >{content}</span> 125 | </div> 126 | <div className="v_n_bottom modal_bottom"> 127 | <div className="v_n_int"> 128 | { 129 | liked ? 130 | <span 131 | className={`v_n_unlike like_unlike ${editing ? 'like_unlike_disabled' : ''}`} 132 | onClick={this.unlike} 133 | ><i class="material-icons">favorite</i></span> 134 | : 135 | <span 136 | className={`v_n_like like_unlike ${editing ? 'like_unlike_disabled' : ''}`} 137 | onClick={this.like} 138 | ><i class="material-icons">favorite_border</i></span> 139 | } 140 | <Link 141 | to={`${match.url}/likes`} 142 | className={`v_n_likes sec_btn ${editing ? 'sec_btn_disabled' : ''}`} 143 | >{`${likes.length} likes`}</Link> 144 | </div> 145 | 146 | { 147 | fn.Me(user) ? 148 | editing ? 149 | <a 150 | href="#" 151 | className="v_n_edit sec_btn" 152 | onClick={this.edit} 153 | >Done editing</a> 154 | : 155 | <a 156 | href="#" 157 | className="v_n_edit sec_btn" 158 | onClick={e => this.toggle_(e, "editing")} 159 | >Edit note</a> 160 | : null 161 | } 162 | 163 | { 164 | fn.Me(user) ? 165 | <a 166 | href="#" 167 | className={`v_n_delete sec_btn ${editing ? 'sec_btn_disabled' : ''} `} 168 | onClick={e => this.toggle_(e, "deleting")} 169 | >Delete note</a> 170 | : null 171 | } 172 | 173 | <a 174 | href='#' 175 | className={`v_n_cancel pri_btn ${editing ? 'a_disabled' : ''} `} 176 | onClick={this.back} 177 | >Done</a> 178 | 179 | </div> 180 | </FadeIn> 181 | 182 | { (deleting) ? <Overlay type="white" /> : null } 183 | { 184 | deleting ? 185 | <Prompt 186 | title={"Delete note"} 187 | content={"This note will be deleted. There's no undo so you won't be able to find it."} 188 | actionText= "Delete" 189 | action={this.delete} 190 | state_updater="deleting" 191 | close={this.toggle_} 192 | /> 193 | : null 194 | } 195 | 196 | <Route path={`${match.url}/likes`} component={() => <Overlay type="white" /> } /> 197 | <Route path={`${match.url}/likes`} component={Likes} /> 198 | 199 | </div> 200 | ) 201 | 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /public/js/src/components/others/end-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | 4 | export default class End extends React.Component{ 5 | 6 | toTop = () => { 7 | $('html, body').animate({ scrollTop: 0 }, 450) 8 | } 9 | 10 | render(){ 11 | return( 12 | <div className="page_end" onClick={this.toTop} > 13 | <span>{this.props.mssg}</span> 14 | </div> 15 | ) 16 | } 17 | } 18 | 19 | End.defaultProps = { 20 | mssg: "Looks like you've reached the end" 21 | } 22 | -------------------------------------------------------------------------------- /public/js/src/components/others/goto-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import * as fn from '../../functions/functions' 4 | import { Link } from 'react-router-dom' 5 | 6 | export default class Goto extends React.Component{ 7 | 8 | toggle = e => { 9 | let op = e.currentTarget.parentNode.nextSibling 10 | fn.toggle(op) 11 | } 12 | 13 | render(){ 14 | let username = $('.data').data('username') 15 | return( 16 | <div class='goto' > 17 | <div className="goto_link"> 18 | <span className="goto_label">Go to</span> 19 | <span class="show_more" onClick={this.toggle} > 20 | <i class="material-icons">expand_more</i> 21 | </span> 22 | </div> 23 | <div className="options goto_options" style={{ display: "none" }} > 24 | <ul className="o_ul"> 25 | <li className="o_li" ><Link to="/" className="o_a">Home</Link></li> 26 | <li className="o_li" ><Link to={`/profile/${username}`} className="o_a">Profile</Link></li> 27 | </ul> 28 | </div> 29 | </div> 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/js/src/components/others/header-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import { NavLink } from 'react-router-dom' 4 | 5 | export default class Header extends React.Component{ 6 | render(){ 7 | let 8 | username = $('.data').data('username'), 9 | id = $('.data').data('session') 10 | 11 | return ( 12 | <div class='header_loggedin' > 13 | <div class="left"> 14 | <NavLink activeClassName="ha_active" exact={true} to="/" >Home</NavLink> 15 | <NavLink activeClassName="ha_active" to="/explore" >Explore</NavLink> 16 | <NavLink activeClassName="ha_active" to="/deactivate" >Deactivate</NavLink> 17 | </div> 18 | <div className="right"> 19 | <NavLink activeClassName="ha_active" to={`/profile/${username}`} className='vp' >{username}</NavLink> 20 | <NavLink activeClassName="ha_active" to="/edit" >Edit profile</NavLink> 21 | <a href="/logout" >Logout</a> 22 | </div> 23 | </div> 24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /public/js/src/components/others/nothing-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | export default class Nothing extends React.Component{ 5 | 6 | render(){ 7 | let { mssg, showMssg } = this.props 8 | return( 9 | <div class='home_last_mssg' style={ !showMssg ? { border: "none" } : null } > 10 | <img src='/images/large.jpg' /> 11 | { showMssg ? <span>{mssg}</span> : null } 12 | </div> 13 | ) 14 | } 15 | } 16 | 17 | Nothing.defaultProps = { 18 | mssg: "Hello, a message for you!", 19 | showMssg: true 20 | } 21 | 22 | Nothing.propTypes = { 23 | mssg: PropTypes.string, 24 | showMssg: PropTypes.bool 25 | } 26 | -------------------------------------------------------------------------------- /public/js/src/components/others/overlay-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | export default class Overlay extends React.Component{ 5 | render(){ 6 | let 7 | { type } = this.props, 8 | cls 9 | 10 | if(type == 'black'){ 11 | cls = 'overlay' 12 | } else if (type == 'white'){ 13 | cls = 'hidden_overlay' 14 | } else if (type == 'colored'){ 15 | cls = 'colored_overlay' 16 | } 17 | 18 | return ( 19 | <div class={cls} ></div> 20 | ) 21 | } 22 | } 23 | 24 | Overlay.defaultProps = { 25 | type: 'black' 26 | } 27 | 28 | Overlay.propTypes = { 29 | type: PropTypes.string 30 | } 31 | -------------------------------------------------------------------------------- /public/js/src/components/others/prompt-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import PropTypes from 'prop-types' 4 | import { FadeIn } from 'animate-components' 5 | 6 | export default class Prompt extends React.Component{ 7 | 8 | componentDidMount = () => $('.prompt-done').focus() 9 | 10 | render(){ 11 | let { title, content, actionText, action, state_updater, close } = this.props 12 | 13 | return ( 14 | <div class="prompt"> 15 | <FadeIn duration="200ms" > 16 | <div class="prompt-top"> 17 | <span class="prompt-title">{title}</span> 18 | <span onClick={() => close(null, state_updater)} ><i class="material-icons">clear</i></span> 19 | </div> 20 | <div class="prompt-middle"> 21 | <span class="prompt-content">{content}</span> 22 | </div> 23 | <div class="prompt-bottom"> 24 | <a href="#" class="sec_btn prompt-cancel" onClick={e => close(e, state_updater)} >Cancel</a> 25 | <a href="#" class="pri_btn prompt-done" onClick={action} >{actionText}</a> 26 | </div> 27 | </FadeIn> 28 | </div> 29 | ) 30 | 31 | } 32 | 33 | } 34 | 35 | Prompt.defaultProps = { 36 | title: "Title", 37 | content: "Main content goes here. Content should be of 2 lines to avoid the blur that Chrome creates!", 38 | actionText: "Action", 39 | action: () => { return false; } 40 | } 41 | 42 | Prompt.propTypes = { 43 | title: PropTypes.string, 44 | content: PropTypes.string, 45 | actionText: PropTypes.string, 46 | action: PropTypes.func 47 | } 48 | -------------------------------------------------------------------------------- /public/js/src/components/others/title-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Helmet } from 'react-helmet' 3 | import PropTypes from 'prop-types' 4 | 5 | export default class Title extends React.Component { 6 | render(){ 7 | return ( 8 | <Helmet> 9 | <title>{ this.props.value } • Mini Social Network 10 | 11 | ) 12 | } 13 | } 14 | 15 | Title.propTypes = { 16 | value: PropTypes.string 17 | } 18 | -------------------------------------------------------------------------------- /public/js/src/components/profile/banner-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import $ from 'jquery' 3 | import { Link } from 'react-router-dom' 4 | import { connect } from 'react-redux' 5 | import * as fn from '../../functions/functions' 6 | 7 | @connect(store => { 8 | return { 9 | user: store.user, 10 | follow: store.follow, 11 | note: store.notes 12 | } 13 | }) 14 | 15 | export default class Banner extends React.Component { 16 | 17 | state = { is_following: false } 18 | 19 | componentWillReceiveProps = ({ follow: { is_following } }) => 20 | this.setState({ is_following }) 21 | 22 | follow = e => { 23 | e.preventDefault() 24 | let { dispatch, user: { user_details: { id, username } } } = this.props 25 | fn.follow({ 26 | user: id, 27 | username, 28 | dispatch, 29 | update_followers: true, 30 | done: () => this.setState({ is_following: true }) 31 | }) 32 | } 33 | 34 | unfollow = e => { 35 | e.preventDefault() 36 | let { dispatch, user: { user_details: { id } } } = this.props 37 | fn.unfollow({ 38 | user: id, 39 | dispatch, 40 | update_followers: true, 41 | done: () => this.setState({ is_following: false }) 42 | }) 43 | } 44 | 45 | toNotes = () => $('html, body').animate({ scrollTop: 390 }, 450) 46 | 47 | render(){ 48 | let 49 | { url, 50 | user: { user_details }, 51 | notes, 52 | follow: { profile_views, followers, followings} 53 | } = this.props, 54 | { is_following } = this.state, 55 | s_username = $('.data').data('username') 56 | 57 | return( 58 |
59 |
60 | 61 |
62 | Your profile 63 |
64 | 65 |
66 | { 67 | fn.Me(user_details.id) ? 68 | {fn.e_v() ? "Create note" : "Verify email to create note"} 75 | : 76 | is_following ? 77 | Unfollow 78 | : 79 | Follow 80 | } 81 |
82 | 83 |
84 | {user_details.username} 85 | {user_details.email} 86 |
87 | { 88 | user_details.bio ? 89 | {user_details.bio} 90 | : 91 | fn.Me(user_details.id) ? 92 | You have no bio!! 93 | : 94 | {`${user_details.username} has no bio!!`} 95 | } 96 |
97 |
98 |
99 |
100 | {notes.length} 101 | Notes 102 |
103 | 104 | {followers.length} 105 | Followers 106 | 107 | 108 | {followings.length} 109 | Followings 110 | 111 |
112 | {profile_views} 113 | Profile views 114 |
115 |
116 |
117 | 118 |
119 | 120 |
121 | ) 122 | 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /public/js/src/components/profile/filter-notes-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class Filter_notes extends React.Component { 4 | 5 | render(){ 6 | let { notes_length, filter } = this.props 7 | 8 | return ( 9 |
10 | { 11 | notes_length != 0 ? 12 | 0} 16 | autoComplete={false} 17 | spellCheck={false} 18 | onChange={filter} 19 | /> 20 | : 21 | null 22 | } 23 |
24 | ) 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/js/src/components/profile/follow/follower-items.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import axios from 'axios' 3 | import $ from 'jquery' 4 | import TimeAgo from 'handy-timeago' 5 | import { connect } from 'react-redux' 6 | import { Link } from 'react-router-dom' 7 | import * as fn from '../../../functions/functions' 8 | 9 | @connect(store => { 10 | return { 11 | user: store.user 12 | } 13 | }) 14 | 15 | export default class Follower_items extends React.Component{ 16 | 17 | state = { is_following: false } 18 | 19 | componentDidMount = async () => { 20 | let { follow_by, follow_by_username } = this.props 21 | if(!fn.Me(follow_by)) { 22 | let { data } = await axios.post('/api/is-following', { username: follow_by_username }) 23 | this.setState({ is_following: data }) 24 | } 25 | } 26 | 27 | follow = e => { 28 | e.preventDefault() 29 | let 30 | { dispatch, follow_by, follow_by_username, user: { user_details } } = this.props, 31 | obj = { 32 | user: follow_by, 33 | username: follow_by_username, 34 | dispatch, 35 | update_followings: fn.Me(user_details.id), 36 | done: () => this.setState({ is_following: true }) 37 | } 38 | fn.follow(obj) 39 | } 40 | 41 | unfollow = e => { 42 | e.preventDefault() 43 | let 44 | { dispatch, follow_by, user: { user_details } } = this.props, 45 | obj = { 46 | user: follow_by, 47 | dispatch, 48 | update_followings: fn.Me(user_details.id), 49 | done: () => this.setState({ is_following: false }) 50 | } 51 | fn.unfollow(obj) 52 | } 53 | 54 | render(){ 55 | let 56 | { follow_by, follow_by_username, follow_time } = this.props, 57 | { is_following } = this.state 58 | 59 | return ( 60 |
61 |
62 | 63 |
64 |
65 |
66 | {follow_by_username} 67 | {TimeAgo(follow_time)} 68 |
69 |
70 | { 71 | fn.Me(follow_by) ? 72 | Profile 73 | : is_following ? 74 | Unfollow 75 | : 76 | 77 | } 78 |
79 |
80 |
81 |
82 | ) 83 | 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /public/js/src/components/profile/follow/followers-comp.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Title from '../../others/title-comp' 3 | import { FadeIn } from 'animate-components' 4 | import { connect } from 'react-redux' 5 | import { Scrollbars } from 'react-custom-scrollbars' 6 | 7 | import Follower_items from './follower-items' 8 | import Goto from '../../others/goto-comp' 9 | import Nothing from '../../others/nothing-comp' 10 | import * as fn from '../../../functions/functions' 11 | 12 | @connect(store => { 13 | return { 14 | follow: store.follow, 15 | user: store.user 16 | } 17 | }) 18 | 19 | export default class Followers extends React.Component{ 20 | 21 | back = e => fn.back(e, this.props.history) 22 | 23 | componentDidMount = () => fn.last_line_remover() 24 | componentWillReceiveProps = props => fn.last_line_remover() 25 | 26 | render(){ 27 | let 28 | { 29 | follow: { followers }, 30 | user: { user_details: { username } } 31 | } = this.props, 32 | map_f = followers.map(f => 33 | 34 | ) 35 | 36 | return ( 37 |