├── .babelrc ├── .docker ├── db │ └── dump.sql ├── install-php.sh ├── nginx.conf └── php.ini ├── .env.docker ├── .env.example ├── .env.travis ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .phpunit.result.cache ├── .travis.yml ├── Dockerfile ├── README.md ├── app ├── Console │ ├── Commands │ │ └── RefreshDatabase.php │ └── Kernel.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Api │ │ │ ├── ArticleController.php │ │ │ ├── Auth │ │ │ │ ├── LoginController.php │ │ │ │ └── RegisterController.php │ │ │ └── UserController.php │ │ ├── Auth │ │ │ ├── ConfirmPasswordController.php │ │ │ ├── ForgotPasswordController.php │ │ │ ├── LoginController.php │ │ │ ├── RegisterController.php │ │ │ ├── ResetPasswordController.php │ │ │ └── VerificationController.php │ │ └── Controller.php │ ├── Kernel.php │ ├── Middleware │ │ ├── EncryptCookies.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ └── VerifyCsrfToken.php │ └── Requests │ │ ├── ArticleRequest.php │ │ └── UserRequest.php ├── Models │ ├── Article.php │ └── User.php └── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── artisan ├── bootstrap ├── app.php ├── autoload.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── api.php ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── cors.php ├── database.php ├── filesystems.php ├── mail.php ├── queue.php ├── sanctum.php ├── services.php ├── session.php └── view.php ├── database ├── .gitignore ├── factories │ ├── ArticleFactory.php │ └── UserFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2017_03_24_122715_create_article_table.php │ └── 2019_12_14_000001_create_personal_access_tokens_table.php └── seeders │ ├── DatabaseSeeder.php │ └── UsersTableSeeder.php ├── docker-compose.yml ├── license.md ├── package-lock.json ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── favicon.ico ├── index.php ├── robots.txt └── web.config ├── resources ├── js │ ├── app.js │ ├── bootstrap.js │ ├── common │ │ ├── articles │ │ │ └── listing │ │ │ │ ├── components │ │ │ │ ├── Article.js │ │ │ │ └── Articles.js │ │ │ │ └── index.js │ │ ├── footer │ │ │ └── index.js │ │ ├── loader │ │ │ └── index.js │ │ ├── navigation │ │ │ ├── NavItem.js │ │ │ ├── PrivateHeader.js │ │ │ ├── PublicHeader.js │ │ │ └── index.js │ │ └── scroll-top │ │ │ ├── ScrollTop.js │ │ │ └── index.js │ ├── layout │ │ ├── Private.js │ │ ├── Public.js │ │ └── index.js │ ├── modules │ │ ├── article │ │ │ ├── Article.js │ │ │ ├── pages │ │ │ │ ├── add │ │ │ │ │ ├── Page.js │ │ │ │ │ ├── components │ │ │ │ │ │ └── Form.js │ │ │ │ │ └── index.js │ │ │ │ ├── edit │ │ │ │ │ ├── Page.js │ │ │ │ │ ├── components │ │ │ │ │ │ └── Form.js │ │ │ │ │ └── index.js │ │ │ │ └── list │ │ │ │ │ ├── Page.js │ │ │ │ │ ├── components │ │ │ │ │ ├── ArticleRow.js │ │ │ │ │ └── Pagination.js │ │ │ │ │ └── index.js │ │ │ ├── routes.js │ │ │ ├── service.js │ │ │ └── store │ │ │ │ ├── action-types.js │ │ │ │ ├── actions.js │ │ │ │ └── reducer.js │ │ ├── auth │ │ │ ├── pages │ │ │ │ ├── login │ │ │ │ │ ├── Page.js │ │ │ │ │ ├── components │ │ │ │ │ │ └── Form.js │ │ │ │ │ └── index.js │ │ │ │ ├── password │ │ │ │ │ ├── Page.js │ │ │ │ │ ├── components │ │ │ │ │ │ └── Form.js │ │ │ │ │ └── index.js │ │ │ │ ├── register │ │ │ │ │ ├── Page.js │ │ │ │ │ ├── components │ │ │ │ │ │ └── Form.js │ │ │ │ │ └── index.js │ │ │ │ └── reset-password │ │ │ │ │ ├── Page.js │ │ │ │ │ ├── components │ │ │ │ │ └── Form.js │ │ │ │ │ └── index.js │ │ │ ├── routes.js │ │ │ ├── service.js │ │ │ └── store │ │ │ │ ├── action-types.js │ │ │ │ ├── actions.js │ │ │ │ └── reduer.js │ │ ├── user │ │ │ ├── User.js │ │ │ ├── pages │ │ │ │ └── edit │ │ │ │ │ ├── Page.js │ │ │ │ │ ├── components │ │ │ │ │ └── Form.js │ │ │ │ │ └── index.js │ │ │ ├── routes.js │ │ │ ├── service.js │ │ │ └── store │ │ │ │ ├── action-types.js │ │ │ │ ├── actions.js │ │ │ │ └── reducer.js │ │ └── web │ │ │ ├── pages │ │ │ ├── blog │ │ │ │ ├── details │ │ │ │ │ ├── Page.js │ │ │ │ │ └── index.js │ │ │ │ └── list │ │ │ │ │ ├── Page.js │ │ │ │ │ └── index.js │ │ │ └── home │ │ │ │ ├── Page.js │ │ │ │ ├── components │ │ │ │ └── Header.js │ │ │ │ └── index.js │ │ │ └── routes.js │ ├── routes │ │ ├── Private.js │ │ ├── Public.js │ │ ├── index.js │ │ └── routes.js │ ├── store │ │ ├── config.js │ │ ├── index.js │ │ └── reducers.js │ ├── utils │ │ ├── Http.js │ │ ├── Model.js │ │ └── Transformer.js │ └── values │ │ └── index.js ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php ├── sass │ ├── _variables.scss │ └── app.scss └── views │ └── index.blade.php ├── routes ├── api.php ├── api │ ├── articles.php │ ├── auth.php │ └── users.php ├── channels.php ├── console.php └── web.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tests ├── CreatesApplication.php ├── Feature │ ├── ArticleTest.php │ └── LoginTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── webpack.mix.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | "@babel/plugin-syntax-dynamic-import", 7 | "@babel/plugin-proposal-class-properties" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.docker/install-php.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | apk add bzip2 file re2c freetds freetype icu libintl libldap libjpeg libmcrypt libpng libpq libwebp libzip nodejs npm 4 | 5 | TMP="autoconf \ 6 | bzip2-dev \ 7 | freetds-dev \ 8 | freetype-dev \ 9 | g++ \ 10 | gcc \ 11 | gettext-dev \ 12 | icu-dev \ 13 | jpeg-dev \ 14 | libmcrypt-dev \ 15 | libpng-dev \ 16 | libwebp-dev \ 17 | libxml2-dev \ 18 | libzip-dev \ 19 | make \ 20 | openldap-dev \ 21 | postgresql-dev" 22 | 23 | apk add $TMP 24 | 25 | # Configure extensions 26 | docker-php-ext-configure gd --with-jpeg-dir=usr/ --with-freetype-dir=usr/ --with-webp-dir=usr/ 27 | docker-php-ext-configure ldap --with-libdir=lib/ 28 | docker-php-ext-configure pdo_dblib --with-libdir=lib/ 29 | 30 | docker-php-ext-install \ 31 | bz2 \ 32 | exif \ 33 | gd \ 34 | gettext \ 35 | intl \ 36 | ldap \ 37 | pdo_dblib \ 38 | pdo_pgsql \ 39 | xmlrpc \ 40 | zip \ 41 | mysqli \ 42 | pdo_mysql 43 | 44 | # Install Xdebug 45 | pecl install xdebug \ 46 | && docker-php-ext-enable xdebug \ 47 | && echo "remote_host=docker.for.mac.localhost" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ 48 | && echo "remote_port=9001" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ 49 | && echo "remote_enable=1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ 50 | && echo "idekey=IDE_DEBUG" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ 51 | && echo "error_reporting=E_ALL" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ 52 | && echo "display_startup_errors=On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \ 53 | && echo "display_errors=On" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini 54 | 55 | # Install composer 56 | cd /tmp && php -r "readfile('https://getcomposer.org/installer');" | php && \ 57 | mv composer.phar /usr/bin/composer && \ 58 | chmod +x /usr/bin/composer 59 | 60 | apk del $TMP 61 | 62 | # Install PHPUnit 63 | curl -sSL -o /usr/bin/phpunit https://phar.phpunit.de/phpunit.phar && chmod +x /usr/bin/phpunit 64 | 65 | # Set timezone 66 | #RUN echo Asia/Karachi > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata 67 | -------------------------------------------------------------------------------- /.docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 8080; 3 | index index.php index.html; 4 | error_log /var/log/nginx/api-error.log; 5 | access_log /var/log/nginx/api-access.log; 6 | root /var/www/app/public; 7 | location ~ \.php$ { 8 | try_files $uri =404; 9 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 10 | fastcgi_pass lr_app:9000; 11 | fastcgi_index index.php; 12 | include fastcgi_params; 13 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 14 | fastcgi_param PATH_INFO $fastcgi_path_info; 15 | } 16 | location / { 17 | try_files $uri $uri/ /index.php?$query_string; 18 | gzip_static on; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.docker/php.ini: -------------------------------------------------------------------------------- 1 | upload_max_filesize=40M 2 | post_max_size=40M 3 | memory_limit = -1 4 | -------------------------------------------------------------------------------- /.env.docker: -------------------------------------------------------------------------------- 1 | APP_ENV=test 2 | APP_KEY=key 3 | APP_DEBUG=true 4 | APP_LOG_LEVEL=debug 5 | APP_URL=http://localhost:8100 6 | 7 | DB_CONNECTION=mysql 8 | DB_HOST=lr_database 9 | DB_PORT=3306 10 | DB_DATABASE=laravel_react 11 | DB_USERNAME=moeen 12 | DB_PASSWORD=basra 13 | 14 | BROADCAST_DRIVER=log 15 | CACHE_DRIVER=file 16 | SESSION_DRIVER=file 17 | QUEUE_DRIVER=sync 18 | 19 | REDIS_HOST=127.0.0.1 20 | REDIS_PASSWORD=null 21 | REDIS_PORT=6379 22 | 23 | MAIL_DRIVER=smtp 24 | MAIL_HOST=mailtrap.io 25 | MAIL_PORT=2525 26 | MAIL_USERNAME=null 27 | MAIL_PASSWORD=null 28 | MAIL_ENCRYPTION=null 29 | 30 | PUSHER_APP_ID= 31 | PUSHER_APP_KEY= 32 | PUSHER_APP_SECRET= 33 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 3 | APP_DEBUG=true 4 | APP_LOG_LEVEL=debug 5 | APP_URL=http://localhost:3000 6 | 7 | DB_CONNECTION=mysql 8 | DB_HOST=127.0.0.1 9 | DB_PORT=3306 10 | DB_DATABASE=database 11 | DB_USERNAME=username 12 | DB_PASSWORD=password 13 | 14 | BROADCAST_DRIVER=log 15 | CACHE_DRIVER=file 16 | SESSION_DRIVER=file 17 | QUEUE_DRIVER=sync 18 | 19 | REDIS_HOST=127.0.0.1 20 | REDIS_PASSWORD=null 21 | REDIS_PORT=6379 22 | 23 | MAIL_DRIVER=smtp 24 | MAIL_HOST=mailtrap.io 25 | MAIL_PORT=2525 26 | MAIL_USERNAME=null 27 | MAIL_PASSWORD=null 28 | MAIL_ENCRYPTION=null 29 | 30 | PUSHER_APP_ID= 31 | PUSHER_APP_KEY= 32 | PUSHER_APP_SECRET= 33 | 34 | PERSONAL_CLIENT_ID=1 35 | PERSONAL_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 36 | PASSWORD_CLIENT_ID=2 37 | PASSWORD_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -------------------------------------------------------------------------------- /.env.travis: -------------------------------------------------------------------------------- 1 | APP_ENV=testing 2 | APP_KEY=SomeRandomString 3 | 4 | DB_CONNECTION=testing 5 | DB_TEST_USERNAME=root 6 | DB_TEST_PASSWORD= 7 | 8 | CACHE_DRIVER=array 9 | SESSION_DRIVER=array 10 | QUEUE_DRIVER=sync 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /public -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | env: { 7 | browser: true, 8 | node: true, 9 | es6: true, 10 | }, 11 | globals: { 12 | React: true 13 | }, 14 | parserOptions: { 15 | ecmaFeatures: { 16 | jsx: true 17 | } 18 | }, 19 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 20 | extends: ["eslint:recommended", "plugin:react/recommended"], 21 | // required to lint *.vue files 22 | plugins: [ 23 | 'html', 'react' 24 | ], 25 | // add your custom rules here 26 | 'rules': { 27 | // allow paren-less arrow functions 28 | 'arrow-parens': 0, 29 | // allow async-await 30 | 'generator-star-spacing': 0, 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /storage/*.key 3 | /vendor 4 | /.idea 5 | Homestead.json 6 | Homestead.yaml 7 | .env 8 | _ide_helper.php 9 | 10 | /public/storage 11 | /public/hot 12 | /public/js/ 13 | /public/css/ 14 | /public/fonts/ 15 | /public/mix-manifest.json 16 | /*.log 17 | /public/*.js 18 | /public/*.js.map 19 | -------------------------------------------------------------------------------- /.phpunit.result.cache: -------------------------------------------------------------------------------- 1 | C:37:"PHPUnit\Runner\DefaultTestResultCache":1256:{a:2:{s:7:"defects";a:7:{s:74:"Tests\Feature\ArticleTest::that_only_loading_articles_for_provided_user_id";i:4;s:49:"Tests\Feature\ArticleTest::that_load_all_articles";i:4;s:62:"Tests\Feature\ArticleTest::that_loaded_only_published_articles";i:4;s:59:"Tests\Feature\ArticleTest::that_load_only_published_article";i:4;s:95:"Tests\Feature\ArticleTest::that_article_get_published_and_total_number_of_published_get_changed";i:4;s:99:"Tests\Feature\ArticleTest::that_article_get_unpublished_and_total_number_of_unpublished_get_changed";i:4;s:44:"Tests\Feature\LoginTest::test_user_can_login";i:3;}s:5:"times";a:8:{s:74:"Tests\Feature\ArticleTest::that_only_loading_articles_for_provided_user_id";d:1.668;s:49:"Tests\Feature\ArticleTest::that_load_all_articles";d:0.541;s:62:"Tests\Feature\ArticleTest::that_loaded_only_published_articles";d:0.376;s:59:"Tests\Feature\ArticleTest::that_load_only_published_article";d:0.556;s:95:"Tests\Feature\ArticleTest::that_article_get_published_and_total_number_of_published_get_changed";d:0.438;s:99:"Tests\Feature\ArticleTest::that_article_get_unpublished_and_total_number_of_unpublished_get_changed";d:0.419;s:44:"Tests\Feature\LoginTest::test_user_can_login";d:1.037;s:37:"Tests\Unit\ExampleTest::testBasicTest";d:0.21;}}} -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.3 5 | 6 | before_script: 7 | - cp .env.travis .env 8 | - composer self-update 9 | - composer install --no-interaction 10 | - php artisan key:generate 11 | - php artisan migrate --no-interaction --verbose 12 | - php artisan passport:configure 13 | 14 | before_install: 15 | - mysql -e 'CREATE DATABASE testing;' 16 | 17 | script: 18 | - ./vendor/bin/phpunit 19 | 20 | services: 21 | - mysql 22 | 23 | cache: 24 | directories: 25 | - vendor 26 | 27 | branches: 28 | only: 29 | - master 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.4-fpm-alpine 2 | 3 | # Comment this to improve stability on "auto deploy" environments 4 | RUN apk update && apk upgrade 5 | 6 | # Install basic dependencies 7 | RUN apk -u add bash git 8 | 9 | # Install PHP extensions 10 | ADD ./.docker/install-php.sh /usr/sbin/install-php.sh 11 | RUN chmod +x /usr/sbin/install-php.sh 12 | RUN /usr/sbin/install-php.sh 13 | 14 | # Copy existing application directory contents 15 | COPY ./.docker/*.ini /usr/local/etc/php/conf.d/ 16 | COPY . . 17 | 18 | # Change current user to www-data 19 | USER www-data 20 | 21 | # Expose ports and start php-fpm server 22 | EXPOSE 9000 23 | CMD ["php-fpm"] 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | ## Laravel 8 and React 17 boilerplate 4 | There are two different ways to run this demo 5 | 6 | Please follow the guide. 7 | 8 | ## Prerequisite 9 | 10 | 1. Make sure you have [composer](https://getcomposer.org/download/) installed. 11 | 2. Make sure you have latest stable version of [node](https://nodejs.org/en/download/) installed. 12 | 13 | ### Option 1 14 | 15 | 1. `git clone` 16 | 2. `create a .env file copy content from .env.example and update the values` 17 | 3. `composer install && composer update` 18 | 4. `php artisan cron:refresh-database` 19 | 5. if npm version < 7 `npm install && npm run dev` else `npm install --legacy-peer-deps && npm run dev` 20 | 6. `php artisan key:gen` 21 | 7. `php artisan serve` 22 | 23 | ### Option 2 24 | 25 | ## Prerequisite 26 | Make sure you have [docker](https://docs.docker.com/install/) and [docker-compose](https://docs.docker.com/compose/install/) installed on you machine. 27 | 28 | 1. `git clone` 29 | 2. `create a .env file copy content from .env.docker and do not make any change` 30 | 31 | run following command in terminal / power shell 32 | ``` 33 | docker-compose up -d 34 | ``` 35 | 36 | when docker will finish building the containers, access the "laravel-react-app" container using following command 37 | 38 | `docker exec -it lr_app sh` 39 | 40 | now you will be inside container 41 | 42 | run following commands 43 | 1. `composer install && composer update` 44 | 2. `php artisan cron:refresh-database` 45 | 3. `php artisan key:gen` 46 | 4. if npm version < 7 `npm install && npm run dev` else `npm install --legacy-peer-deps && npm run dev` 47 | 48 | open browser and check the following address 49 | 50 | `http://localhost:8100` 51 | 52 | TODO: 53 | 54 | - [x] Add Redux 55 | - [x] Add Laravel Sanctum for authentication 56 | - [x] User Login 57 | - [x] User Register 58 | - [x] Users Crud 59 | - [x] Articles Crud 60 | - [x] Form validation Client and Server 61 | - [x] Reset Password 62 | - [x] Tests 63 | - [x] Upgrade to Laravel 7 64 | - [x] Upgrade to React 16.13 65 | - [x] docker 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /app/Console/Commands/RefreshDatabase.php: -------------------------------------------------------------------------------- 1 | call('migrate:refresh'); 31 | $this->call('db:seed'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 29 | // ->hourly(); 30 | $schedule->command(RefreshDatabase::class)->hourly(); 31 | } 32 | 33 | /** 34 | * Register the Closure based commands for the application. 35 | * 36 | * @return void 37 | */ 38 | protected function commands() 39 | { 40 | $this->load(__DIR__.'/Commands'); 41 | require base_path('routes/console.php'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 67 | return response()->json(['error' => 'Unauthenticated.'], 401); 68 | } 69 | 70 | return redirect()->guest(route('login')); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/ArticleController.php: -------------------------------------------------------------------------------- 1 | user()->is_admin) { 23 | return Article::loadAll(); 24 | } 25 | return Article::loadAllMine($request->user()->id); 26 | } 27 | 28 | /** 29 | * get all published articles 30 | * 31 | * @return mixed 32 | */ 33 | public function publishedArticles() 34 | { 35 | return Article::loadAllPublished(); 36 | } 37 | 38 | /** 39 | * Get single published article 40 | * 41 | * @param $slug 42 | * @return mixed 43 | */ 44 | public function publishedArticle($slug) 45 | { 46 | return Article::loadPublished($slug); 47 | } 48 | 49 | /** 50 | * Show the form for creating a new resource. 51 | * 52 | * @return \Illuminate\Http\Response 53 | */ 54 | public function create() 55 | { 56 | // 57 | } 58 | 59 | /** 60 | * Store a newly created resource in storage. 61 | * 62 | * @param ArticleRequest $request 63 | * @return \Illuminate\Http\Response 64 | */ 65 | public function store(ArticleRequest $request) 66 | { 67 | $user = $request->user(); 68 | 69 | $article = new Article($request->validated()); 70 | $article->slug = Str::slug($request->get('title')); 71 | 72 | $user->articles()->save($article); 73 | 74 | return response()->json($article, 201); 75 | } 76 | 77 | /** 78 | * Display the specified resource. 79 | * 80 | * @param \Illuminate\Http\Request $request 81 | * @param int $id 82 | * @return \Illuminate\Http\Response 83 | */ 84 | public function show(Request $request, $id) 85 | { 86 | if (!$request->user()->is_admin) { 87 | return Article::mine($request->user()->id)->findOrFail($id); 88 | } 89 | 90 | return Article::findOrFail($id); 91 | } 92 | 93 | /** 94 | * Show the form for editing the specified resource. 95 | * 96 | * @param int $id 97 | * @return \Illuminate\Http\Response 98 | */ 99 | public function edit($id) 100 | { 101 | // 102 | } 103 | 104 | /** 105 | * Update the specified resource in storage. 106 | * 107 | * @param ArticleRequest $request 108 | * @param int $id 109 | * @return \Illuminate\Http\Response 110 | */ 111 | public function update(ArticleRequest $request, $id) 112 | { 113 | $article = Article::findOrFail($id); 114 | 115 | $data = $request->validated(); 116 | $data['slug'] = Str::slug($data['title']); 117 | $article->update($data); 118 | 119 | return response()->json($article, 200); 120 | } 121 | 122 | /** 123 | * Remove the specified resource from storage. 124 | * 125 | * @param int $id 126 | * @return \Illuminate\Http\Response 127 | */ 128 | public function delete($id) 129 | { 130 | $article = Article::findOrFail($id); 131 | 132 | $article->delete(); 133 | 134 | return response([], 200); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Auth/LoginController.php: -------------------------------------------------------------------------------- 1 | validate($request, [ 15 | 'email' => 'required|email|exists:users,email', 16 | 'password' => 'required|min:6', 17 | ], [ 18 | 'email.exists' => 'The user credentials were incorrect.', 19 | ]); 20 | 21 | request()->request->add([ 22 | 'grant_type' => 'password', 23 | 'client_id' => env('PASSWORD_CLIENT_ID'), 24 | 'client_secret' => env('PASSWORD_CLIENT_SECRET'), 25 | 'username' => $input['email'], 26 | 'password' => $input['password'], 27 | ]); 28 | 29 | $response = Route::dispatch(Request::create('/oauth/token', 'POST')); 30 | 31 | $data = json_decode($response->getContent(), true); 32 | 33 | if (!$response->isOk()) { 34 | return response()->json($data, 401); 35 | } 36 | 37 | return $data; 38 | } 39 | 40 | public function logout(Request $request) 41 | { 42 | $accessToken = $request->user()->token(); 43 | 44 | DB::table('oauth_refresh_tokens') 45 | ->where('access_token_id', $accessToken->id) 46 | ->update([ 47 | 'revoked' => true, 48 | ]); 49 | 50 | $accessToken->revoke(); 51 | 52 | return response()->json([], 201); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | validate($request, [ 16 | 'name' => 'required|min:3', 17 | 'email' => 'required|email|unique:users,email', 18 | 'password' => 'required|min:6|confirmed', 19 | 'password_confirmation' => 'required|min:6' 20 | ], [ 21 | 'password.confirmed' => 'The password does not match.' 22 | ]); 23 | 24 | try { 25 | event(new Registered($this->create($request->all()))); 26 | 27 | $http = new Client; 28 | 29 | $response = $http->post(env('APP_URL') . '/oauth/token', [ 30 | 'form_params' => [ 31 | 'grant_type' => 'password', 32 | 'client_id' => env('PASSWORD_CLIENT_ID'), 33 | 'client_secret' => env('PASSWORD_CLIENT_SECRET'), 34 | 'username' => $request->get('email'), 35 | 'password' => $request->get('password'), 36 | 'remember' => false, 37 | 'scope' => '', 38 | ], 39 | ]); 40 | 41 | return json_decode((string)$response->getBody(), true); 42 | } catch (\Exception $e) { 43 | dd($e->getMessage(), $e->getCode(), $e->getTrace()); 44 | return response()->json([ 45 | "error" => "invalid_credentials", 46 | "message" => "The user credentials were incorrect." 47 | ], 401); 48 | } 49 | } 50 | 51 | /** 52 | * Create a new user instance after a valid registration. 53 | * 54 | * @param array $data 55 | * @return User 56 | */ 57 | protected function create(array $data) 58 | { 59 | return User::create([ 60 | 'name' => $data['name'], 61 | 'email' => $data['email'], 62 | 'password' => bcrypt($data['password']), 63 | ]); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/UserController.php: -------------------------------------------------------------------------------- 1 | update($request->validated()); 15 | 16 | return response()->json([ 17 | 'user' => $user 18 | ], 201); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ConfirmPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ForgotPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest')->except('logout'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 42 | } 43 | 44 | /** 45 | * Get a validator for an incoming registration request. 46 | * 47 | * @param array $data 48 | * @return \Illuminate\Contracts\Validation\Validator 49 | */ 50 | protected function validator(array $data) 51 | { 52 | return Validator::make($data, [ 53 | 'name' => ['required', 'string', 'max:255'], 54 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 55 | 'password' => ['required', 'string', 'min:8', 'confirmed'], 56 | ]); 57 | } 58 | 59 | /** 60 | * Create a new user instance after a valid registration. 61 | * 62 | * @param array $data 63 | * @return \App\Models\User 64 | */ 65 | protected function create(array $data) 66 | { 67 | return User::create([ 68 | 'name' => $data['name'], 69 | 'email' => $data['email'], 70 | 'password' => Hash::make($data['password']), 71 | ]); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 39 | $this->middleware('signed')->only('verify'); 40 | $this->middleware('throttle:6,1')->only('verify', 'resend'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | [ 30 | \App\Http\Middleware\EncryptCookies::class, 31 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 32 | \Illuminate\Session\Middleware\StartSession::class, 33 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 34 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 35 | \App\Http\Middleware\VerifyCsrfToken::class, 36 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 37 | ], 38 | 39 | 'api' => [ 40 | \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 41 | 'throttle:api', 42 | 'bindings', 43 | ], 44 | ]; 45 | 46 | /** 47 | * The application's route middleware. 48 | * 49 | * These middleware may be assigned to groups or used individually. 50 | * 51 | * @var array 52 | */ 53 | protected $routeMiddleware = [ 54 | 'auth' => \Illuminate\Auth\Middleware\Authenticate::class, 55 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 56 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 57 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 58 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 59 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 60 | ]; 61 | } 62 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/home'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | '', 28 | 'title' => 'required|min:3', 29 | 'description' => 'required|min:10', 30 | 'content' => 'required|min:10', 31 | 'published' => 'nullable|boolean', 32 | 'published_at' => 'nullable|date', 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Requests/UserRequest.php: -------------------------------------------------------------------------------- 1 | 'required|min:3', 30 | 'email' => [ 31 | 'required', 32 | Rule::unique('users')->ignore(Auth::user()->id) 33 | ], 34 | 'phone' => 'nullable|min:8|numeric', 35 | 'about' => 'nullable|min:10|max:1024' 36 | ]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Models/Article.php: -------------------------------------------------------------------------------- 1 | 'boolean', 42 | ]; 43 | 44 | protected static function newFactory(): ArticleFactory 45 | { 46 | return ArticleFactory::new(); 47 | } 48 | 49 | /** 50 | * Load all for admin and paginate 51 | * 52 | * @return Paginator 53 | */ 54 | public static function loadAll(): Paginator 55 | { 56 | return static::latest() 57 | ->paginate(); 58 | } 59 | 60 | /** 61 | * Load all for logged in user and paginate 62 | * 63 | * @param $user_id 64 | * 65 | * @return Paginator 66 | */ 67 | public static function loadAllMine(int $user_id): Paginator 68 | { 69 | return static::latest() 70 | ->mine($user_id) 71 | ->paginate(); 72 | } 73 | 74 | /** 75 | * load all published with pagination 76 | * 77 | * @return Paginator 78 | */ 79 | public static function loadAllPublished(): Paginator 80 | { 81 | return static::with([ 82 | 'user' => function (BelongsTo $query) { 83 | $query->select('id', 'name'); 84 | }, 85 | ]) 86 | ->latest() 87 | ->published() 88 | ->paginate(); 89 | } 90 | 91 | /** 92 | * load one published 93 | * 94 | * @param string $slug 95 | * 96 | * @return Article 97 | */ 98 | public static function loadPublished(string $slug): Article 99 | { 100 | return static::with([ 101 | 'user' => function (BelongsTo $query) { 102 | $query->select('id', 'name'); 103 | }, 104 | ]) 105 | ->published() 106 | ->where('slug', $slug) 107 | ->firstOrFail(); 108 | } 109 | 110 | /** 111 | * Add query scope to get only published articles 112 | * 113 | * @param Builder $query 114 | * 115 | * @return Builder 116 | */ 117 | public function scopePublished(Builder $query): Builder 118 | { 119 | return $query->where([ 120 | 'published' => true, 121 | ]); 122 | } 123 | 124 | /** 125 | * Load only articles related with the user id 126 | * 127 | * @param Builder $query 128 | * @param int $user_id 129 | * 130 | * @return Builder 131 | */ 132 | public function scopeMine(Builder $query, int $user_id): Builder 133 | { 134 | return $query->where('user_id', $user_id); 135 | } 136 | 137 | /** 138 | * Relationship between articles and user 139 | * 140 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 141 | */ 142 | public function user(): BelongsTo 143 | { 144 | return $this->belongsTo(User::class); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 'boolean', 44 | ]; 45 | 46 | protected static function newFactory(): UserFactory 47 | { 48 | return UserFactory::new(); 49 | } 50 | 51 | /** 52 | * @return HasMany 53 | */ 54 | public function articles(): HasMany 55 | { 56 | return $this->hasMany(Article::class); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 16 | ]; 17 | 18 | /** 19 | * Register any authentication / authorization services. 20 | * 21 | * @return void 22 | */ 23 | public function boot() 24 | { 25 | $this->registerPolicies(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'App\Listeners\EventListener', 18 | ], 19 | ]; 20 | 21 | /** 22 | * Register any events for your application. 23 | * 24 | * @return void 25 | */ 26 | public function boot() 27 | { 28 | parent::boot(); 29 | 30 | // 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 48 | 49 | $this->mapWebRoutes(); 50 | 51 | // 52 | } 53 | 54 | /** 55 | * Define the "web" routes for the application. 56 | * 57 | * These routes all receive session state, CSRF protection, etc. 58 | * 59 | * @return void 60 | */ 61 | protected function mapWebRoutes() 62 | { 63 | Route::middleware('web') 64 | ->namespace($this->namespace) 65 | ->group(base_path('routes/web.php')); 66 | } 67 | 68 | /** 69 | * Define the "api" routes for the application. 70 | * 71 | * These routes are typically stateless. 72 | * 73 | * @return void 74 | */ 75 | protected function mapApiRoutes() 76 | { 77 | Route::prefix('api') 78 | ->middleware('api') 79 | ->namespace($this->namespace . '\\Api') 80 | ->group(base_path('routes/api.php')); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 32 | 33 | $status = $kernel->handle( 34 | $input = new Symfony\Component\Console\Input\ArgvInput, 35 | new Symfony\Component\Console\Output\ConsoleOutput 36 | ); 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Shutdown The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once Artisan has finished running. We will fire off the shutdown events 44 | | so that any final work may be done by the application before we shut 45 | | down the process. This is the last thing to happen to the request. 46 | | 47 | */ 48 | 49 | $kernel->terminate($input, $status); 50 | 51 | exit($status); 52 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/autoload.php: -------------------------------------------------------------------------------- 1 | env('API_VERSION', 'v1'), 6 | ]; 7 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'web', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session", "token" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | 44 | 'api' => [ 45 | 'driver' => 'token', 46 | 'provider' => 'users', 47 | 'hash' => false, 48 | ], 49 | ], 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | User Providers 54 | |-------------------------------------------------------------------------- 55 | | 56 | | All authentication drivers have a user provider. This defines how the 57 | | users are actually retrieved out of your database or other storage 58 | | mechanisms used by this application to persist your user's data. 59 | | 60 | | If you have multiple user tables or models you may configure multiple 61 | | sources which represent each model / table. These sources may then 62 | | be assigned to any extra authentication guards you have defined. 63 | | 64 | | Supported: "database", "eloquent" 65 | | 66 | */ 67 | 68 | 'providers' => [ 69 | 'users' => [ 70 | 'driver' => 'eloquent', 71 | 'model' => App\Models\User::class, 72 | ], 73 | 74 | // 'users' => [ 75 | // 'driver' => 'database', 76 | // 'table' => 'users', 77 | // ], 78 | ], 79 | 80 | /* 81 | |-------------------------------------------------------------------------- 82 | | Resetting Passwords 83 | |-------------------------------------------------------------------------- 84 | | 85 | | You may specify multiple password reset configurations if you have more 86 | | than one user table or model in the application and you want to have 87 | | separate password reset settings based on the specific user types. 88 | | 89 | | The expire time is the number of minutes that the reset token should be 90 | | considered valid. This security feature keeps tokens short-lived so 91 | | they have less time to be guessed. You may change this as needed. 92 | | 93 | */ 94 | 95 | 'passwords' => [ 96 | 'users' => [ 97 | 'provider' => 'users', 98 | 'table' => 'password_resets', 99 | 'expire' => 60, 100 | 'throttle' => 60, 101 | ], 102 | ], 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Password Confirmation Timeout 107 | |-------------------------------------------------------------------------- 108 | | 109 | | Here you may define the amount of seconds before a password confirmation 110 | | times out and the user is prompted to re-enter their password via the 111 | | confirmation screen. By default, the timeout lasts for three hours. 112 | | 113 | */ 114 | 115 | 'password_timeout' => 10800, 116 | 117 | ]; 118 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'useTLS' => true, 41 | ], 42 | ], 43 | 44 | 'ably' => [ 45 | 'driver' => 'ably', 46 | 'key' => env('ABLY_KEY'), 47 | ], 48 | 49 | 'redis' => [ 50 | 'driver' => 'redis', 51 | 'connection' => 'default', 52 | ], 53 | 54 | 'log' => [ 55 | 'driver' => 'log', 56 | ], 57 | 58 | 'null' => [ 59 | 'driver' => 'null', 60 | ], 61 | 62 | ], 63 | 64 | ]; 65 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | | Supported drivers: "apc", "array", "database", "file", 30 | | "memcached", "redis", "dynamodb", "null" 31 | | 32 | */ 33 | 34 | 'stores' => [ 35 | 36 | 'apc' => [ 37 | 'driver' => 'apc', 38 | ], 39 | 40 | 'array' => [ 41 | 'driver' => 'array', 42 | 'serialize' => false, 43 | ], 44 | 45 | 'database' => [ 46 | 'driver' => 'database', 47 | 'table' => 'cache', 48 | 'connection' => null, 49 | ], 50 | 51 | 'file' => [ 52 | 'driver' => 'file', 53 | 'path' => storage_path('framework/cache/data'), 54 | ], 55 | 56 | 'memcached' => [ 57 | 'driver' => 'memcached', 58 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 59 | 'sasl' => [ 60 | env('MEMCACHED_USERNAME'), 61 | env('MEMCACHED_PASSWORD'), 62 | ], 63 | 'options' => [ 64 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 65 | ], 66 | 'servers' => [ 67 | [ 68 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 69 | 'port' => env('MEMCACHED_PORT', 11211), 70 | 'weight' => 100, 71 | ], 72 | ], 73 | ], 74 | 75 | 'redis' => [ 76 | 'driver' => 'redis', 77 | 'connection' => 'cache', 78 | ], 79 | 80 | 'dynamodb' => [ 81 | 'driver' => 'dynamodb', 82 | 'key' => env('AWS_ACCESS_KEY_ID'), 83 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 84 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 85 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 86 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 87 | ], 88 | 89 | ], 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Cache Key Prefix 94 | |-------------------------------------------------------------------------- 95 | | 96 | | When utilizing a RAM based store such as APC or Memcached, there might 97 | | be other applications utilizing the same cache. So, we'll specify a 98 | | value to get prefixed to all our keys so we can avoid collisions. 99 | | 100 | */ 101 | 102 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), 103 | 104 | ]; 105 | -------------------------------------------------------------------------------- /config/cors.php: -------------------------------------------------------------------------------- 1 | ['api/*', 'sanctum/csrf-cookie'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | 'local', 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Cloud Filesystem Disk 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Many applications store files both locally and in the cloud. For this 24 | | reason, you may specify a default "cloud" driver here. This driver 25 | | will be bound as the Cloud disk implementation in the container. 26 | | 27 | */ 28 | 29 | 'cloud' => 's3', 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Filesystem Disks 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here you may configure as many filesystem "disks" as you wish, and you 37 | | may even configure multiple disks of the same driver. Defaults have 38 | | been setup for each driver as an example of the required options. 39 | | 40 | | Supported Drivers: "local", "ftp", "s3", "rackspace" 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'public' => [ 52 | 'driver' => 'local', 53 | 'root' => storage_path('app/public'), 54 | 'url' => env('APP_URL').'/storage', 55 | 'visibility' => 'public', 56 | ], 57 | 58 | 's3' => [ 59 | 'driver' => 's3', 60 | 'key' => env('AWS_KEY'), 61 | 'secret' => env('AWS_SECRET'), 62 | 'region' => env('AWS_REGION'), 63 | 'bucket' => env('AWS_BUCKET'), 64 | ], 65 | 66 | ], 67 | 68 | ]; 69 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Queue Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure the connection information for each server that 26 | | is used by your application. A default configuration has been added 27 | | for each back-end shipped with Laravel. You are free to add more. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'retry_after' => 90, 49 | ], 50 | 51 | 'sqs' => [ 52 | 'driver' => 'sqs', 53 | 'key' => 'your-public-key', 54 | 'secret' => 'your-secret-key', 55 | 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', 56 | 'queue' => 'your-queue-name', 57 | 'region' => 'us-east-1', 58 | ], 59 | 60 | 'redis' => [ 61 | 'driver' => 'redis', 62 | 'connection' => 'default', 63 | 'queue' => 'default', 64 | 'retry_after' => 90, 65 | ], 66 | 67 | ], 68 | 69 | /* 70 | |-------------------------------------------------------------------------- 71 | | Failed Queue Jobs 72 | |-------------------------------------------------------------------------- 73 | | 74 | | These options configure the behavior of failed queue job logging so you 75 | | can control which database and table are used to store the jobs that 76 | | have failed. You may change them to any database / table you wish. 77 | | 78 | */ 79 | 80 | 'failed' => [ 81 | 'database' => env('DB_CONNECTION', 'mysql'), 82 | 'table' => 'failed_jobs', 83 | ], 84 | 85 | ]; 86 | -------------------------------------------------------------------------------- /config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env( 17 | 'SANCTUM_STATEFUL_DOMAINS', 18 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1' 19 | )), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Expiration Minutes 24 | |-------------------------------------------------------------------------- 25 | | 26 | | This value controls the number of minutes until an issued token will be 27 | | considered expired. If this value is null, personal access tokens do 28 | | not expire. This won't tweak the lifetime of first-party sessions. 29 | | 30 | */ 31 | 32 | 'expiration' => null, 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Sanctum Middleware 37 | |-------------------------------------------------------------------------- 38 | | 39 | | When authenticating your first-party SPA with Sanctum you may need to 40 | | customize some of the middleware Sanctum uses while processing the 41 | | request. You may change the middleware listed below as required. 42 | | 43 | */ 44 | 45 | 'middleware' => [ 46 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 47 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 48 | ], 49 | 50 | ]; 51 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | ], 21 | 22 | 'ses' => [ 23 | 'key' => env('SES_KEY'), 24 | 'secret' => env('SES_SECRET'), 25 | 'region' => 'us-east-1', 26 | ], 27 | 28 | 'sparkpost' => [ 29 | 'secret' => env('SPARKPOST_SECRET'), 30 | ], 31 | 32 | 'stripe' => [ 33 | 'model' => App\Models\User::class, 34 | 'key' => env('STRIPE_KEY'), 35 | 'secret' => env('STRIPE_SECRET'), 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | realpath(base_path('resources/views')), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /database/factories/ArticleFactory.php: -------------------------------------------------------------------------------- 1 | faker->sentence; 20 | 21 | return [ 22 | 'user_id' => User::factory(), 23 | 'title' => $title, 24 | 'slug' => Str::slug($title), 25 | 'description' => $this->faker->sentence(15), 26 | 'content' => implode(' ', $this->faker->paragraphs(2)), 27 | 'published' => true, 28 | 'published_at' => Carbon::now(), 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->name, 19 | 'email' => $this->faker->unique()->safeEmail, 20 | 'phone' => $this->faker->phoneNumber, 21 | 'about' => $this->faker->sentence(10), 22 | 'password' => $password = bcrypt('secret'), 23 | 'remember_token' => Str::random(10), 24 | ]; 25 | } 26 | 27 | public function isAdmin() 28 | { 29 | return $this->state(function() { 30 | return [ 31 | 'is_admin' => true 32 | ]; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->string('password'); 21 | $table->string('phone')->nullable(); 22 | $table->string('about')->nullable(); 23 | $table->boolean('is_admin')->default(0); 24 | $table->timestamp('email_verified_at')->nullable(); 25 | $table->rememberToken(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('users'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2017_03_24_122715_create_article_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->unsignedInteger('user_id'); 19 | $table->string('title'); 20 | $table->string('slug'); 21 | $table->text('description'); 22 | $table->text('content'); 23 | $table->boolean('published')->default(false); 24 | $table->timestamp('published_at')->nullable(); 25 | $table->softDeletes(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('articles'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->morphs('tokenable'); 19 | $table->string('name'); 20 | $table->string('token', 64)->unique(); 21 | $table->text('abilities')->nullable(); 22 | $table->timestamp('last_used_at')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('personal_access_tokens'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(UsersTableSeeder::class); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /database/seeders/UsersTableSeeder.php: -------------------------------------------------------------------------------- 1 | has(Article::factory(250)) 21 | ->create([ 22 | 'name' => 'Moeen Basra', 23 | 'email' => 'm.basra@live.com', 24 | 'password' => bcrypt('secret'), 25 | 'is_admin' => true, 26 | 'remember_token' => Str::random(10), 27 | ]); 28 | 29 | User::factory(50)->create(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | #Laravel Service 4 | lr_app: 5 | container_name: lr_app 6 | restart: always 7 | tty: true 8 | working_dir: /var/www/app 9 | build: 10 | context: . 11 | dockerfile: Dockerfile 12 | environment: 13 | XDEBUG_CONFIG: "idekey=IDE_DEBUG" 14 | PHP_IDE_CONFIG: "serverName=laravel_react_app" 15 | volumes: 16 | - .:/var/www/app 17 | depends_on: 18 | - lr_database 19 | links: 20 | - lr_database 21 | networks: 22 | - lr_network 23 | 24 | #DB Service 25 | lr_database: 26 | image: mariadb:latest 27 | container_name: lr_database 28 | restart: always 29 | working_dir: /etc/mysql 30 | tty: true 31 | environment: 32 | MYSQL_DATABASE: laravel_react 33 | MYSQL_USER: moeen 34 | MYSQL_ROOT_PASSWORD: basra 35 | MYSQL_PASSWORD: basra 36 | ports: 37 | - 3333:3306 38 | volumes: 39 | - ./.docker/db/dump.sql:/docker-entrypoint-initdb.d/dump.sql 40 | command: --default-authentication-plugin=mysql_native_password 41 | networks: 42 | - lr_network 43 | 44 | #Nginx Service 45 | lr_server: 46 | image: nginx:alpine 47 | container_name: lr_server 48 | restart: always 49 | tty: true 50 | ports: 51 | - 8100:8080 52 | volumes: 53 | - .:/var/www/app 54 | - ./.docker/nginx.conf:/etc/nginx/conf.d/default.conf 55 | working_dir: /var/www 56 | depends_on: 57 | - lr_app 58 | networks: 59 | - lr_network 60 | 61 | #Docker Networks 62 | networks: 63 | lr_network: 64 | driver: bridge 65 | volumes: 66 | dbdata: 67 | driver: local 68 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Moeen Farooq 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel-react", 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "mix", 6 | "watch": "mix watch", 7 | "watch-poll": "mix watch -- --watch-options-poll=1000", 8 | "hot": "mix watch --hot", 9 | "prod": "npm run production", 10 | "production": "mix --production" 11 | }, 12 | "author": "Moeen Basra", 13 | "license": "ISC", 14 | "browserslist": [ 15 | ">0.2%", 16 | "not dead", 17 | "not ie <= 11", 18 | "not op_mini all" 19 | ], 20 | "dependencies": { 21 | "@popperjs/core": "^2.6.0", 22 | "axios": "^0.21.1", 23 | "bootstrap": "^4.6.0", 24 | "clsx": "^1.1.1", 25 | "font-awesome": "^4.7.0", 26 | "history": "^5.0.0", 27 | "jquery": "^3.5.1", 28 | "lodash": "^4.17.20", 29 | "moment": "^2.29.1", 30 | "prop-types": "^15.7.2", 31 | "react": "^17.0.1", 32 | "react-document-title": "^2.0.3", 33 | "react-dom": "^17.0.1", 34 | "react-loadable": "^5.5.0", 35 | "react-redux": "^7.2.2", 36 | "react-router-dom": "^5.2.0", 37 | "reactstrap": "^8.9.0", 38 | "redux": "^4.0.5", 39 | "redux-thunk": "^2.3.0", 40 | "ree-validate": "^3.3.2" 41 | }, 42 | "devDependencies": { 43 | "@babel/plugin-proposal-class-properties": "^7.12.1", 44 | "@babel/preset-env": "^7.12.11", 45 | "@babel/preset-react": "^7.12.10", 46 | "babel-eslint": "^10.1.0", 47 | "babel-plugin-syntax-dynamic-import": "^6.18.0", 48 | "browser-sync": "^2.26.14", 49 | "browser-sync-webpack-plugin": "^2.3.0", 50 | "cross-env": "^7.0.3", 51 | "eslint": "^7.19.0", 52 | "eslint-config-standard": "^16.0.2", 53 | "eslint-friendly-formatter": "^4.0.1", 54 | "eslint-plugin-html": "^6.1.1", 55 | "eslint-plugin-import": "^2.22.1", 56 | "eslint-plugin-node": "^11.1.0", 57 | "eslint-plugin-promise": "^4.2.1", 58 | "eslint-plugin-react": "^7.22.0", 59 | "eslint-webpack-plugin": "^2.4.3", 60 | "laravel-mix": "^6.0.11", 61 | "postcss": "^8.2.4", 62 | "redux-logger": "^3.0.6", 63 | "remove-files-webpack-plugin": "^1.4.4", 64 | "resolve-url-loader": "^3.1.2", 65 | "sass": "^1.32.5", 66 | "sass-loader": "^10.1.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/Feature 14 | 15 | 16 | 17 | ./tests/Unit 18 | 19 | 20 | 21 | 22 | ./app 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes If Not A Folder... 9 | RewriteCond %{REQUEST_FILENAME} !-d 10 | RewriteRule ^(.*)/$ /$1 [L,R=301] 11 | 12 | # Handle Front Controller... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_FILENAME} !-f 15 | RewriteRule ^ index.php [L] 16 | 17 | # Handle Authorization Header 18 | RewriteCond %{HTTP:Authorization} . 19 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 20 | 21 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moeen-basra/laravel-react/a6c3284cc301c47e68990730b2509f7aa7a3f06d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | /* 11 | |-------------------------------------------------------------------------- 12 | | Register The Auto Loader 13 | |-------------------------------------------------------------------------- 14 | | 15 | | Composer provides a convenient, automatically generated class loader for 16 | | our application. We just need to utilize it! We'll simply require it 17 | | into the script here so that we don't have to worry about manual 18 | | loading any of our classes later on. It feels nice to relax. 19 | | 20 | */ 21 | 22 | require __DIR__.'/../bootstrap/autoload.php'; 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Turn On The Lights 27 | |-------------------------------------------------------------------------- 28 | | 29 | | We need to illuminate PHP development, so let us turn on the lights. 30 | | This bootstraps the framework and gets it ready for use, then it 31 | | will load up this application so that we can run it and send 32 | | the responses back to the browser and delight our users. 33 | | 34 | */ 35 | 36 | $app = require_once __DIR__.'/../bootstrap/app.php'; 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Run The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once we have the application, we can handle the incoming request 44 | | through the kernel, and send the associated response back to 45 | | the client's browser allowing them to enjoy the creative 46 | | and wonderful application we have prepared for them. 47 | | 48 | */ 49 | 50 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 51 | 52 | $response = $kernel->handle( 53 | $request = Illuminate\Http\Request::capture() 54 | ); 55 | 56 | $response->send(); 57 | 58 | $kernel->terminate($request, $response); 59 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * First we will load all of this project's JavaScript dependencies which 3 | * includes React and other helpers. It's a great starting point while 4 | * building robust, powerful web applications using React + Laravel. 5 | */ 6 | 7 | require('./bootstrap'); 8 | 9 | /** 10 | * Next, we will create a fresh React component instance and attach it to 11 | * the page. Then, you may begin adding components to this application 12 | * or customize the JavaScript scaffolding to fit your unique needs. 13 | */ 14 | import React from 'react' 15 | import { render } from 'react-dom' 16 | import { Provider } from 'react-redux' 17 | import store from './store' 18 | import Routes from './routes' 19 | 20 | import { authCheck } from './modules/auth/store/actions' 21 | 22 | store.dispatch(authCheck()) 23 | 24 | render(( 25 | 26 | ), 27 | document.getElementById('app'), 28 | ) 29 | -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | window._ = require('lodash'); 2 | 3 | /** 4 | * We'll load the axios HTTP library which allows us to easily issue requests 5 | * to our Laravel back-end. This library automatically handles sending the 6 | * CSRF token as a header based on the value of the "XSRF" token cookie. 7 | */ 8 | 9 | window.axios = require('axios'); 10 | 11 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 12 | window.axios.defaults.withCredentials = true; 13 | 14 | -------------------------------------------------------------------------------- /resources/js/common/articles/listing/components/Article.js: -------------------------------------------------------------------------------- 1 | // import libs 2 | import React from 'react' 3 | import PropTypes from 'prop-types' 4 | 5 | // import components 6 | import { Link } from 'react-router-dom' 7 | 8 | const displayName = 'ArticleComponent' 9 | const propTypes = { 10 | index: PropTypes.number.isRequired, 11 | article: PropTypes.object.isRequired, 12 | } 13 | 14 | // const renderAuthor = (article) => { 15 | // return article.user && `By ${ article.user.name }` 16 | // } 17 | 18 | const renderPublishedAt = (article) => { 19 | return article.publishedAt && `at ${article.publishedAt.format('MMMM D, YYYY')}` 20 | } 21 | 22 | function render ({ article }) { 23 | return
24 |
25 |
26 |

{article.title}

27 |
{renderPublishedAt(article)}
28 |

{ article.description }

29 | Read More 30 |
31 |
32 |
33 | } 34 | 35 | render.displayName = displayName 36 | render.propTypes = propTypes 37 | 38 | export default render 39 | -------------------------------------------------------------------------------- /resources/js/common/articles/listing/components/Articles.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | import PropTypes from 'prop-types' 3 | import Article from './Article' 4 | 5 | class Articles extends Component { 6 | static displayName = 'Articles' 7 | static propTypes = { 8 | articles: PropTypes.array.isRequired, 9 | dispatch: PropTypes.func.isRequired, 10 | } 11 | 12 | constructor(props) { 13 | super(props) 14 | 15 | this.state = { 16 | // 17 | } 18 | } 19 | 20 | renderArticles() { 21 | return this.props.articles.map((article, index) => { 22 | return
25 | }) 26 | } 27 | 28 | render() { 29 | return (
30 |
31 |
32 | { this.props.articles && this.renderArticles() } 33 |
34 |
35 |
) 36 | } 37 | } 38 | 39 | export default Articles 40 | -------------------------------------------------------------------------------- /resources/js/common/articles/listing/index.js: -------------------------------------------------------------------------------- 1 | // libs 2 | import { connect } from 'react-redux' 3 | import Article from '../../../modules/article/Article' 4 | 5 | // components 6 | import Articles from './components/Articles' 7 | 8 | const mapStateToProps = state => { 9 | const {data, ...meta} = state.articles 10 | 11 | return { 12 | articles: data?.map((article) => new Article(article)), 13 | meta: Object.assign({}, meta) 14 | } 15 | } 16 | 17 | export default connect(mapStateToProps)(Articles) 18 | -------------------------------------------------------------------------------- /resources/js/common/footer/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import moment from "moment"; 3 | 4 | const Footer = () => () 9 | 10 | export default Footer 11 | -------------------------------------------------------------------------------- /resources/js/common/loader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | // set display name for component 5 | const displayName = 'CommonLoader' 6 | 7 | // validate component properties 8 | const propTypes = { 9 | isLoading: PropTypes.bool, 10 | error: PropTypes.object, 11 | } 12 | 13 | const LoadingComponent = ({isLoading, error}) => { 14 | // Handle the loading state 15 | if (isLoading) { 16 | return
Loading...
17 | } 18 | // Handle the error state 19 | else if (error) { 20 | 21 | // This resolves an issue that newly named code-splitted js files make 22 | if(error['name'] && error['name'] == "ChunkLoadError"){ 23 | window.location.reload(); 24 | } 25 | 26 | return
Sorry, there was a problem loading the page.
27 | } 28 | else { 29 | return null 30 | } 31 | } 32 | 33 | LoadingComponent.displayName = displayName 34 | LoadingComponent.propTypes = propTypes 35 | 36 | export default LoadingComponent -------------------------------------------------------------------------------- /resources/js/common/navigation/NavItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import { NavLink } from 'react-router-dom' 4 | import { NavItem } from 'reactstrap' 5 | 6 | const propTypes = { 7 | path: PropTypes.string.isRequired, 8 | children: PropTypes.any, 9 | } 10 | 11 | const contextTypes = { 12 | router: PropTypes.object, 13 | } 14 | 15 | const Link = ({ path, children }) => { 16 | return 17 | 18 | {children} 19 | 20 | 21 | } 22 | 23 | Link.propTypes = propTypes 24 | Link.contextTypes = contextTypes 25 | 26 | export default Link 27 | -------------------------------------------------------------------------------- /resources/js/common/navigation/PrivateHeader.js: -------------------------------------------------------------------------------- 1 | // import libs 2 | import React from 'react' 3 | import PropTypes from 'prop-types' 4 | import { Link } from 'react-router-dom' 5 | // import components 6 | import { Collapse, Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap' 7 | import NavItem from './NavItem' 8 | 9 | // initiate Component 10 | export default function PrivateHeader({user, showNavigation, showDropdown, toggleDropdown, logout}) { 11 | return ( 12 | 13 | 17 | 18 | 34 | 35 | ); 36 | } 37 | 38 | // bind properties 39 | PrivateHeader.displayName = 'PrivateHeader' 40 | PrivateHeader.propTypes = { 41 | user: PropTypes.object.isRequired, 42 | showNavigation: PropTypes.bool.isRequired, 43 | showDropdown: PropTypes.bool.isRequired, 44 | toggleDropdown: PropTypes.func.isRequired, 45 | logout: PropTypes.func.isRequired, 46 | } 47 | -------------------------------------------------------------------------------- /resources/js/common/navigation/PublicHeader.js: -------------------------------------------------------------------------------- 1 | // import libs 2 | import React from 'react' 3 | import PropTypes from 'prop-types' 4 | 5 | // import components 6 | import { Collapse } from 'reactstrap' 7 | import NavItem from './NavItem' 8 | 9 | // define component name 10 | const displayName = 'PublicHeader' 11 | 12 | // validate properties 13 | const propTypes = { 14 | showNavigation: PropTypes.bool.isRequired, 15 | } 16 | 17 | // initiate comppnent 18 | const PublicHeader = ({ showNavigation }) => ( 19 | 20 | 23 | 27 | ) 28 | 29 | // bind properties 30 | PublicHeader.displayName = displayName 31 | PublicHeader.propTypes = propTypes 32 | 33 | // export component 34 | export default PublicHeader 35 | -------------------------------------------------------------------------------- /resources/js/common/navigation/index.js: -------------------------------------------------------------------------------- 1 | // import libs 2 | import React, { Component } from 'react' 3 | import PropTypes from 'prop-types' 4 | import { connect } from 'react-redux' 5 | import { logout } from '../../modules/auth/service' 6 | 7 | // import components 8 | import { Link } from 'react-router-dom' 9 | import { Navbar, NavbarToggler } from 'reactstrap'; 10 | import PrivateHeader from './PrivateHeader' 11 | import PublicHeader from './PublicHeader' 12 | 13 | class Navigation extends Component { 14 | static propTypes = { 15 | isAuthenticated: PropTypes.bool.isRequired, 16 | user: PropTypes.object.isRequired, 17 | dispatch: PropTypes.func.isRequired, 18 | } 19 | 20 | constructor(props) { 21 | super(props) 22 | 23 | this.state = { 24 | showNavigation: false, 25 | showDropdown: false, 26 | } 27 | } 28 | 29 | toggleNavbar = () => { 30 | this.setState({ 31 | showNavigation: !this.state.showNavigation, 32 | }); 33 | } 34 | 35 | toggleDropdown = () => { 36 | this.setState({ 37 | showDropdown: !this.state.showDropdown, 38 | }) 39 | } 40 | 41 | logout = e => { 42 | e.preventDefault() 43 | 44 | this.props.dispatch(logout()) 45 | } 46 | 47 | render() { 48 | return ( 49 | 50 | MOEEN.ME 51 | 52 | { 53 | this.props.isAuthenticated 54 | ? 59 | : 60 | } 61 | 62 | ) 63 | } 64 | } 65 | 66 | const mapStateToProps = state => { 67 | return { 68 | isAuthenticated: state.auth.isAuthenticated, 69 | user: state.user 70 | } 71 | } 72 | 73 | export default connect(mapStateToProps)(Navigation) 74 | -------------------------------------------------------------------------------- /resources/js/common/scroll-top/ScrollTop.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | const style = { 4 | float: 'right', 5 | position: 'fixed', 6 | bottom: '1rem', 7 | right: '1rem', 8 | } 9 | 10 | class ScrollTop extends Component { 11 | constructor() { 12 | super() 13 | 14 | this.delayInMs = '16' 15 | this.scrollStepInPx = 50 16 | 17 | this.state = { 18 | intervalId: 0, 19 | showScoller: false, 20 | } 21 | 22 | this.toggleScroll = this.toggleScroll.bind(this) 23 | this.scrollStep = this.scrollStep.bind(this) 24 | } 25 | 26 | componentDidMount() { 27 | window.addEventListener("scroll", this.toggleScroll) 28 | } 29 | 30 | componentWillUnmount() { 31 | window.removeEventListener("scroll", this.toggleScroll) 32 | } 33 | 34 | toggleScroll() { 35 | if (window.pageYOffset > 200) { 36 | this.setState({ showScoller: true }) 37 | } else { 38 | this.setState({ showScoller: false }) 39 | } 40 | } 41 | 42 | scrollStep() { 43 | if (window.pageYOffset === 0) { 44 | clearInterval(this.state.intervalId) 45 | } 46 | window.scroll(0, window.pageYOffset - this.scrollStepInPx) 47 | } 48 | 49 | scrollToTop(e) { 50 | e.preventDefault() 51 | 52 | let intervalId = setInterval(this.scrollStep, this.delayInMs) 53 | this.setState({ intervalId: intervalId }) 54 | } 55 | 56 | render() { 57 | if (this.state.showScoller) { 58 | return ( { this.scrollToTop(e) }}> 61 |