加入權限機制, Auth相關views, 後台views及相關機制

This commit is contained in:
kroutony 2020-02-22 14:45:16 +08:00
parent 8684bd1d21
commit ca7e23e6de
44 changed files with 1584 additions and 45 deletions

3
.gitignore vendored
View File

@ -4,6 +4,9 @@
/public/storage
/public/js
/public/css
/public/fonts/vendor
/public/images/vendor
/public/mix-manifest.json
/storage/*.key
/vendor
.env

View File

@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use App\Repositories\UserRepository;
use Illuminate\Console\Command;
class CreateAdminAccount extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'account:create-admin {email} {password}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create an admin account';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$email = $this->argument('email');
$password = $this->argument('password');
$userRepo = app(UserRepository::class);
$userRepo->createAdminAccount($email, $password);
$this->info("Account: $email has been created");
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers;
/**
* 後台頁面的Controller選單項目之外的其他頁面
*
* Class AdminPageController
* @package App\Http\Controllers
*/
class AdminPageController extends Controller
{
private $mainManuPagePrefix;
public function __construct()
{
$this->mainManuPagePrefix = config('admin.route_name_prefix') . config('admin.menu.route_name_prefix');
}
public function index()
{
return view('admin.index');
}
}

View File

@ -61,6 +61,7 @@ class Kernel extends HttpKernel
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'admin.area' => \App\Http\Middleware\AdminAreaGuard::class,
];
/**

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Middleware;
use Closure;
/**
* 判斷後台權限
*
* Class AdminAreaGuard
* @package App\Http\Middleware
*/
class AdminAreaGuard
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$user = $request->user();
if($user) {
if($user->can('admin area')) {
return $next($request);
} else {
abort(403); // 有登入但無權限
}
} else {
abort(404); // 無登入回應404顯示找不到頁面
}
}
}

View File

@ -21,7 +21,7 @@ class RouteServiceProvider extends ServiceProvider
*
* @var string
*/
public const HOME = '/home';
public const HOME = '/';
/**
* Define your route model bindings, pattern filters, etc.

View File

@ -0,0 +1,95 @@
<?php
namespace App\Repositories;
use App\TranslatableModel;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
abstract class BaseRepository
{
/**
* @var Model
*/
protected $model;
/**
* @return Model
*/
public function getModel()
{
return $this->model;
}
/**
* @param Model $model
* @return void
*/
public function setModel(Model $model)
{
$this->model = $model;
}
/**
* 取得Model的class name
*
* @return string
*/
public function getModelClass()
{
return get_class($this->model);
}
/**
* 新建一個Model的instance
*
* @return Model
*/
public function createModel()
{
return app($this->getModelClass());
}
/**
* 查詢所有
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public function queryAll()
{
$modelClass = $this->getModelClass();
return $modelClass::all();
}
/**
* 查詢特定ID
*
* @param $id
* @return Model|null
*/
public function findModel($id)
{
return $this->getModel()->find($id);
}
/**
* 刪除特定id
*
* @param $id
* @return bool|null
* @throws \Exception
*/
public function deleteModel($id)
{
return $this->findModel($id)->delete();
}
/**
* @param $id
* @return bool
*/
public function hasModel($id)
{
return $this->getModel()->where('id', $id)->exists();
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Repositories;
use App\User;
use Illuminate\Support\Facades\Hash;
use Str;
/**
* Class UserRepository
* @package App\Repositories
*
* @property \App\User $model
* @method \App\User getModel()
*/
class UserRepository extends BaseRepository
{
public function __construct(User $user)
{
$this->setModel($user);
}
/**
* 建立管理員帳號
*
* @param string $email
* @param string $password
* @return void
*/
public function createAdminAccount($email, $password)
{
if($email && $password) {
$user = $this->model->create([
'email' => $email,
'password' => Hash::make($password),
'api_token' => Str::random(36)
]);
$user->assignRole('administrator');
return $user->id;
}
}
}

View File

@ -5,6 +5,7 @@ namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
/**
* App\User
@ -34,6 +35,7 @@ class User extends Authenticatable
{
use Notifiable;
use HasRoles;
/**
* The attributes that are mass assignable.
*

View File

@ -11,12 +11,14 @@
"php": "^7.2",
"fideloper/proxy": "^4.0",
"laravel/framework": "^6.2",
"laravel/tinker": "^2.0"
"laravel/tinker": "^2.0",
"spatie/laravel-permission": "^3.8"
},
"require-dev": {
"barryvdh/laravel-ide-helper": "^2.6",
"facade/ignition": "^1.4",
"fzaninotto/faker": "^1.9.1",
"laravel/ui": "^1.2",
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^3.0",
"phpunit/phpunit": "^8.0"
@ -37,6 +39,7 @@
},
"classmap": [
"database/seeds",
"database/seeds/Preset",
"database/factories"
]
},

124
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "d823097c9a46903a75367077995590b2",
"content-hash": "7315d6229270be366528dbffc0c6864c",
"packages": [
{
"name": "dnoegel/php-xdg-base-dir",
@ -1459,6 +1459,74 @@
],
"time": "2020-02-21T04:36:14+00:00"
},
{
"name": "spatie/laravel-permission",
"version": "3.8.0",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-permission.git",
"reference": "2339b6fae8f8aa5af047e1a0c54b6fb37546058a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-permission/zipball/2339b6fae8f8aa5af047e1a0c54b6fb37546058a",
"reference": "2339b6fae8f8aa5af047e1a0c54b6fb37546058a",
"shasum": ""
},
"require": {
"illuminate/auth": "^5.8|^6.0|^7.0",
"illuminate/container": "^5.8|^6.0|^7.0",
"illuminate/contracts": "^5.8|^6.0|^7.0",
"illuminate/database": "^5.8|^6.0|^7.0",
"php": "^7.2"
},
"require-dev": {
"orchestra/testbench": "^3.8|^4.0|^5.0",
"phpunit/phpunit": "^8.0",
"predis/predis": "^1.1"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\Permission\\PermissionServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Spatie\\Permission\\": "src"
},
"files": [
"src/helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "Permission handling for Laravel 5.8 and up",
"homepage": "https://github.com/spatie/laravel-permission",
"keywords": [
"acl",
"laravel",
"permission",
"permissions",
"rbac",
"roles",
"security",
"spatie"
],
"time": "2020-02-18T21:16:24+00:00"
},
{
"name": "swiftmailer/swiftmailer",
"version": "v6.2.3",
@ -4118,6 +4186,60 @@
],
"time": "2019-09-25T14:49:45+00:00"
},
{
"name": "laravel/ui",
"version": "v1.2.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/ui.git",
"reference": "bb64fca681566ca94457d490a00f899516e75664"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/ui/zipball/bb64fca681566ca94457d490a00f899516e75664",
"reference": "bb64fca681566ca94457d490a00f899516e75664",
"shasum": ""
},
"require": {
"illuminate/console": "~5.8|^6.0",
"illuminate/filesystem": "~5.8|^6.0",
"illuminate/support": "~5.8|^6.0",
"php": "^7.1.3"
},
"require-dev": {
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^8.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Ui\\UiServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Ui\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel UI utilities and presets.",
"keywords": [
"laravel",
"ui"
],
"time": "2020-02-13T21:12:28+00:00"
},
{
"name": "mockery/mockery",
"version": "1.3.1",

9
config/admin.php Normal file
View File

@ -0,0 +1,9 @@
<?php
use App\Http\Controllers\Admin\Menu;
return [
// 後台的登入路徑
'route' => 'adm',
// 後台的Route Name前綴
'route_name_prefix' => 'admin.',
];

View File

@ -166,6 +166,7 @@ return [
* Package Service Providers...
*/
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,
Spatie\Permission\PermissionServiceProvider::class,
/*
* Application Service Providers...
*/

17
config/data-presets.php Normal file
View File

@ -0,0 +1,17 @@
<?php
return [
'roles' => [
['name' => 'administrator', 'displayName' => 'administrator'],
['name' => 'editor', 'displayName' => 'editor'],
],
'permissions' => [
[
//進入後台
'name' => 'admin area',
'displayName' => 'adminArea',
'assignTo' => [
'administrator', 'editor'
]
],
]
];

129
config/permission.php Normal file
View File

@ -0,0 +1,129 @@
<?php
return [
'models' => [
/*
* When using the "HasPermissions" trait from this package, we need to know which
* Eloquent model should be used to retrieve your permissions. Of course, it
* is often just the "Permission" model but you may use whatever you like.
*
* The model you want to use as a Permission model needs to implement the
* `Spatie\Permission\Contracts\Permission` contract.
*/
'permission' => Spatie\Permission\Models\Permission::class,
/*
* When using the "HasRoles" trait from this package, we need to know which
* Eloquent model should be used to retrieve your roles. Of course, it
* is often just the "Role" model but you may use whatever you like.
*
* The model you want to use as a Role model needs to implement the
* `Spatie\Permission\Contracts\Role` contract.
*/
'role' => Spatie\Permission\Models\Role::class,
],
'table_names' => [
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'roles' => 'roles',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your permissions. We have chosen a basic
* default value but you may easily change it to any table you like.
*/
'permissions' => 'permissions',
/*
* When using the "HasPermissions" trait from this package, we need to know which
* table should be used to retrieve your models permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_permissions' => 'model_has_permissions',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your models roles. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'model_has_roles' => 'model_has_roles',
/*
* When using the "HasRoles" trait from this package, we need to know which
* table should be used to retrieve your roles permissions. We have chosen a
* basic default value but you may easily change it to any table you like.
*/
'role_has_permissions' => 'role_has_permissions',
],
'column_names' => [
/*
* Change this if you want to name the related model primary key other than
* `model_id`.
*
* For example, this would be nice if your primary keys are all UUIDs. In
* that case, name this `model_uuid`.
*/
'model_morph_key' => 'model_id',
],
/*
* When set to true, the required permission/role names are added to the exception
* message. This could be considered an information leak in some contexts, so
* the default setting is false here for optimum safety.
*/
'display_permission_in_exception' => false,
'cache' => [
/*
* By default all permissions are cached for 24 hours to speed up performance.
* When permissions or roles are updated the cache is flushed automatically.
*/
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
/*
* The cache key used to store all permissions.
*/
'key' => 'spatie.permission.cache',
/*
* When checking for a permission against a model by passing a Permission
* instance to the check, this key determines what attribute on the
* Permissions model is used to cache against.
*
* Ideally, this should match your preferred way of checking permissions, eg:
* `$user->can('view-posts')` would be 'name'.
*/
'model_key' => 'name',
/*
* You may optionally indicate a specific cache driver to use for permission and
* role caching using any of the `store` drivers listed in the cache.php config
* file. Using 'default' here means to use the `default` set in cache.php.
*/
'store' => 'default',
],
];

View File

@ -0,0 +1,104 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePermissionTables extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
$tableNames = config('permission.table_names');
$columnNames = config('permission.column_names');
Schema::create($tableNames['permissions'], function (Blueprint $table) {
$table->increments('id');
$table->string('name')->comment('名稱');
$table->string('display_name')->comment('用來顯示的名稱');
$table->string('guard_name');
$table->timestamps();
});
Schema::create($tableNames['roles'], function (Blueprint $table) {
$table->increments('id');
$table->string('name')->comment('名稱');
$table->string('display_name')->comment('用來顯示的名稱');
$table->string('guard_name');
$table->timestamps();
});
Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames) {
$table->unsignedInteger('permission_id');
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type', ]);
$table->foreign('permission_id')
->references('id')
->on($tableNames['permissions'])
->onDelete('cascade');
$table->primary(['permission_id', $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
});
Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames) {
$table->unsignedInteger('role_id');
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type', ]);
$table->foreign('role_id')
->references('id')
->on($tableNames['roles'])
->onDelete('cascade');
$table->primary(['role_id', $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
});
Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) {
$table->unsignedInteger('permission_id');
$table->unsignedInteger('role_id');
$table->foreign('permission_id')
->references('id')
->on($tableNames['permissions'])
->onDelete('cascade');
$table->foreign('role_id')
->references('id')
->on($tableNames['roles'])
->onDelete('cascade');
$table->primary(['permission_id', 'role_id']);
});
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
->forget(config('permission.cache.key'));
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$tableNames = config('permission.table_names');
Schema::drop($tableNames['role_has_permissions']);
Schema::drop($tableNames['model_has_roles']);
Schema::drop($tableNames['model_has_permissions']);
Schema::drop($tableNames['roles']);
Schema::drop($tableNames['permissions']);
}
}

View File

@ -11,6 +11,6 @@ class DatabaseSeeder extends Seeder
*/
public function run()
{
// $this->call(UsersTableSeeder::class);
$this->call(RolesAndPermissionsSeeder::class);
}
}

View File

@ -0,0 +1,45 @@
<?php
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RolesAndPermissionsSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//清除Role與Permission的cache
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
//讀取Config檔案
$presetConfig = config('data-presets');
$roles = $presetConfig['roles'];
$permissions = $presetConfig['permissions'];
//建立Roles
foreach ($roles as $role) {
$_role = Role::create([
'name' => $role['name'],
'display_name' => $role['displayName']
]);
}
//建立Permissions
foreach ($permissions as $permission) {
$_permission = Permission::create([
'name' => $permission['name'],
'display_name' => $permission['displayName']
]);
if(!empty($permission['assignTo'])) {
foreach ($permission['assignTo'] as $roleName) {
$_permission->assignRole($roleName);
}
}
}
}
}

201
package-lock.json generated
View File

@ -919,6 +919,47 @@
"to-fast-properties": "2.0.0"
}
},
"@coreui/coreui": {
"version": "2.1.16",
"resolved": "https://registry.npmjs.org/@coreui/coreui/-/coreui-2.1.16.tgz",
"integrity": "sha512-1YOnQAlcX2bIgnaX3k9GKaN4lD+wKam7tdDfFj7/ZQTN1XG3dwDELHp4aagWQs78ix2CCO1LyeLrzGpsMcLW3Q==",
"dev": true,
"requires": {
"@coreui/coreui-plugin-npm-postinstall": "1.0.2",
"bootstrap": "4.4.1",
"core-js": "3.6.4",
"regenerator-runtime": "0.13.3"
}
},
"@coreui/coreui-plugin-chartjs-custom-tooltips": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@coreui/coreui-plugin-chartjs-custom-tooltips/-/coreui-plugin-chartjs-custom-tooltips-1.3.1.tgz",
"integrity": "sha512-ovNE9QygRdB7IkE7gZNRx79lSk77STtNOFS4NRpjljoRcAseR156ZYV0i/dSoiwZwRJ+dHzWeXy1IMcXcdnAww==",
"dev": true,
"requires": {
"ms": "2.1.2"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
},
"@coreui/coreui-plugin-npm-postinstall": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@coreui/coreui-plugin-npm-postinstall/-/coreui-plugin-npm-postinstall-1.0.2.tgz",
"integrity": "sha512-yeeoWp+bNS84nP1977Y8UCiQ9pssO+f4QuVj3i0/gYZFjjvOgxx0dnyWhtowD5sLYnCRMPlPpqyjwXze3SlkYg==",
"dev": true
},
"@coreui/icons": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@coreui/icons/-/icons-0.4.1.tgz",
"integrity": "sha512-k3U1zzJwwKIH+LgSBD1y+GTbB3Rqen567GyNHNKxHHTke6r67n/s3K2nxgD8vX53WirfXzsz+8+mRipnzkplCA==",
"dev": true
},
"@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@ -1718,6 +1759,15 @@
"file-uri-to-path": "1.0.0"
}
},
"block-ui": {
"version": "2.70.1",
"resolved": "https://registry.npmjs.org/block-ui/-/block-ui-2.70.1.tgz",
"integrity": "sha1-yGLWTuYoj7eBIzd8ZoC8erJiED8=",
"dev": true,
"requires": {
"jquery": "3.4.1"
}
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
@ -1785,6 +1835,12 @@
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
"bootstrap": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz",
"integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==",
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@ -1914,8 +1970,8 @@
"dev": true,
"requires": {
"caniuse-lite": "1.0.30001028",
"electron-to-chromium": "1.3.356",
"node-releases": "1.1.49"
"electron-to-chromium": "1.3.358",
"node-releases": "1.1.50"
}
},
"buffer": {
@ -2083,6 +2139,35 @@
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=",
"dev": true
},
"chart.js": {
"version": "2.9.3",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz",
"integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==",
"dev": true,
"requires": {
"chartjs-color": "2.4.1",
"moment": "2.24.0"
}
},
"chartjs-color": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz",
"integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==",
"dev": true,
"requires": {
"chartjs-color-string": "0.6.0",
"color-convert": "1.9.3"
}
},
"chartjs-color-string": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz",
"integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"chokidar": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
@ -2464,6 +2549,12 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true
},
"core-js": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==",
"dev": true
},
"core-js-compat": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz",
@ -2858,6 +2949,46 @@
"type": "1.2.0"
}
},
"datatables.net": {
"version": "1.10.20",
"resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.20.tgz",
"integrity": "sha512-4E4S7tTU607N3h0fZPkGmAtr9mwy462u+VJ6gxYZ8MxcRIjZqHy3Dv1GNry7i3zQCktTdWbULVKBbkAJkuHEnQ==",
"dev": true,
"requires": {
"jquery": "3.4.1"
}
},
"datatables.net-bs4": {
"version": "1.10.20",
"resolved": "https://registry.npmjs.org/datatables.net-bs4/-/datatables.net-bs4-1.10.20.tgz",
"integrity": "sha512-kQmMUMsHMOlAW96ztdoFqjSbLnlGZQ63iIM82kHbmldsfYdzuyhbb4hTx6YNBi481WCO3iPSvI6YodNec46ZAw==",
"dev": true,
"requires": {
"datatables.net": "1.10.20",
"jquery": "3.4.1"
}
},
"datatables.net-responsive": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/datatables.net-responsive/-/datatables.net-responsive-2.2.3.tgz",
"integrity": "sha512-8D6VtZcyuH3FG0Hn5A4LPZQEOX3+HrRFM7HjpmsQc/nQDBbdeBLkJX4Sh/o1nzFTSneuT1Wh/lYZHVPpjcN+Sw==",
"dev": true,
"requires": {
"datatables.net": "1.10.20",
"jquery": "3.4.1"
}
},
"datatables.net-responsive-bs4": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/datatables.net-responsive-bs4/-/datatables.net-responsive-bs4-2.2.3.tgz",
"integrity": "sha512-SQaWI0uLuPcaiBBin9zX+MuQfTSIkK1bYxbXqUV6NLkHCVa6PMQK7Rvftj0ywG4R7uOtjbzY8nSVqxEKvQI0Vg==",
"dev": true,
"requires": {
"datatables.net-bs4": "1.10.20",
"datatables.net-responsive": "2.2.3",
"jquery": "3.4.1"
}
},
"de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
@ -3169,9 +3300,9 @@
"dev": true
},
"electron-to-chromium": {
"version": "1.3.356",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.356.tgz",
"integrity": "sha512-qW4YHMfOFjvx0jkSK2vjaHoLjk1+uJIV5tqtLDo7P5y3/kM8KQP23YBU0Y5fCSW4jIbDvEzeHDaY4+4vEaqqOw==",
"version": "1.3.358",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.358.tgz",
"integrity": "sha512-y9xvv+9PplXSUkOSxgtOfwNrqD/948VIScyWURnY27PXprg3PmRl7e8ekRJhnksDNjxLVyBYY6I2nQmNBzdi6g==",
"dev": true
},
"elliptic": {
@ -3884,6 +4015,12 @@
"resolve-dir": "1.0.1"
}
},
"flag-icon-css": {
"version": "3.4.6",
"resolved": "https://registry.npmjs.org/flag-icon-css/-/flag-icon-css-3.4.6.tgz",
"integrity": "sha512-rF69rt19Hr63SRQTiPBzQABaYB20LAgZhDkr/AxqSdgmCIN+tC5PRMz56Y0gxehFXJmdRwv55+GMi7R1fCRTwg==",
"dev": true
},
"flush-write-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
@ -3903,6 +4040,12 @@
"debug": "3.1.0"
}
},
"font-awesome": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz",
"integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=",
"dev": true
},
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@ -5531,6 +5674,12 @@
}
}
},
"jquery": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz",
"integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==",
"dev": true
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -6085,6 +6234,12 @@
}
}
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
"dev": true
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -6250,9 +6405,9 @@
}
},
"node-releases": {
"version": "1.1.49",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.49.tgz",
"integrity": "sha512-xH8t0LS0disN0mtRCh+eByxFPie+msJUBL/lJDBuap53QGiYPa9joh83K4pCZgWJ+2L4b9h88vCVdXQ60NO2bg==",
"version": "1.1.50",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.50.tgz",
"integrity": "sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==",
"dev": true,
"requires": {
"semver": "6.3.0"
@ -6731,6 +6886,12 @@
"sha.js": "2.4.11"
}
},
"perfect-scrollbar": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.0.tgz",
"integrity": "sha512-NrNHJn5mUGupSiheBTy6x+6SXCFbLlm8fVZh9moIzw/LgqElN5q4ncR4pbCBCYuCJ8Kcl9mYM0NgDxvW+b4LxA==",
"dev": true
},
"pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
@ -6761,6 +6922,12 @@
"find-up": "3.0.0"
}
},
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"dev": true
},
"portfinder": {
"version": "1.0.25",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
@ -8104,6 +8271,12 @@
"integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
"dev": true
},
"select2": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/select2/-/select2-4.0.13.tgz",
"integrity": "sha512-1JeB87s6oN/TDxQQYCvS5EFoQyvV6eYMZZ0AeA4tdFDYWN3BAGZ8npr17UBFddU0lgAt3H0yjX3X6/ekOj1yjw==",
"dev": true
},
"selfsigned": {
"version": "1.10.7",
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
@ -8326,6 +8499,12 @@
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"simple-line-icons": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/simple-line-icons/-/simple-line-icons-2.4.1.tgz",
"integrity": "sha1-t1vFoNh+UwkowszaVzUnS7JW8jQ=",
"dev": true
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@ -9468,6 +9647,12 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true
},
"vue": {
"version": "2.6.11",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz",
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==",
"dev": true
},
"vue-hot-reload-api": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",

View File

@ -10,13 +10,29 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"@coreui/coreui": "^2.1.16",
"@coreui/coreui-plugin-chartjs-custom-tooltips": "^1.3.1",
"@coreui/icons": "^0.4.1",
"axios": "^0.19",
"cross-env": "^7.0",
"block-ui": "^2.70.1",
"bootstrap": "^4.0.0",
"chart.js": "^2.9.3",
"cross-env": "^7.0.0",
"datatables.net-bs4": "^1.10.20",
"datatables.net-responsive-bs4": "^2.2.3",
"flag-icon-css": "^3.4.6",
"font-awesome": "^4.7.0",
"jquery": "^3.2",
"laravel-mix": "^5.0.1",
"lodash": "^4.17.13",
"resolve-url-loader": "^3.1.0",
"sass": "^1.15.2",
"sass-loader": "^8.0.0",
"vue-template-compiler": "^2.6.11"
"perfect-scrollbar": "^1.5.0",
"popper.js": "^1.12",
"resolve-url-loader": "^3.1.1",
"sass": "^1.20.1",
"sass-loader": "^8.0.2",
"select2": "^4.0.13",
"simple-line-icons": "^2.4.1",
"vue": "^2.6.11",
"vue-template-compiler": "^2.6.10"
}
}

1
resources/js/admin/app.js vendored Normal file
View File

@ -0,0 +1 @@
import './lib';

36
resources/js/admin/lib.js vendored Normal file
View File

@ -0,0 +1,36 @@
import $ from 'jquery';
window.$ = window.jQuery = $;
import Popper from 'popper.js';
window.Popper = Popper;
import 'select2';
import 'bootstrap';
import _ from 'lodash'
window._ = _;
import Axios from 'axios';
window.axios = Axios;
import Vue from 'vue';
window.Vue = Vue;
import 'perfect-scrollbar';
import '@coreui/coreui';
import 'chart.js';
import '@coreui/coreui-plugin-chartjs-custom-tooltips';
try {
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token = document.head.querySelector('meta[name="csrf-token"]');
if (token) {
window.axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('Meta CSRF token not found');
}
} catch(e) {}

View File

@ -1,28 +1,13 @@
window._ = require('lodash');
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
* CSRF token as a header based on the value of the "XSRF" token cookie.
*/
try {
window.Popper = require('popper.js').default;
window.$ = window.jQuery = require('jquery');
require('bootstrap');
} catch (e) {}
window.axios = require('axios');
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/**
* Echo exposes an expressive API for subscribing to channels and listening
* for events that are broadcast by Laravel. Echo and event broadcasting
* allows your team to easily build robust real-time web applications.
*/
// import Echo from 'laravel-echo';
// window.Pusher = require('pusher-js');
// window.Echo = new Echo({
// broadcaster: 'pusher',
// key: process.env.MIX_PUSHER_APP_KEY,
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
// forceTLS: true
// });

19
resources/sass/_variables.scss vendored Normal file
View File

@ -0,0 +1,19 @@
// Body
$body-bg: #f8fafc;
// Typography
$font-family-sans-serif: 'Nunito', sans-serif;
$font-size-base: 0.9rem;
$line-height-base: 1.6;
// Colors
$blue: #3490dc;
$indigo: #6574cd;
$purple: #9561e2;
$pink: #f66d9b;
$red: #e3342f;
$orange: #f6993f;
$yellow: #ffed4a;
$green: #38c172;
$teal: #4dc0b5;
$cyan: #6cb2eb;

3
resources/sass/admin/_bootstrap.scss vendored Normal file
View File

@ -0,0 +1,3 @@
@import "~bootstrap/scss/functions";
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/mixins";

19
resources/sass/admin/_variables.scss vendored Normal file
View File

@ -0,0 +1,19 @@
// Body
$body-bg: #f8fafc;
// Typography
$font-family-sans-serif: 'Nunito', sans-serif;
$font-size-base: 0.9rem;
$line-height-base: 1.6;
// Colors
$blue: #3490dc;
$indigo: #6574cd;
$purple: #9561e2;
$pink: #f66d9b;
$red: #e3342f;
$orange: #f6993f;
$yellow: #ffed4a;
$green: #38c172;
$teal: #4dc0b5;
$cyan: #6cb2eb;

10
resources/sass/admin/app.scss vendored Normal file
View File

@ -0,0 +1,10 @@
//Boostrap函式庫
@import "bootstrap";
//元件
@import "components/datatables";
@import "components/blockui";

View File

@ -0,0 +1,5 @@
.blockUI {
&.blockOverlay {
z-index: 1098 !important;
}
}

View File

@ -0,0 +1,14 @@
//結果數目
div.dataTables_wrapper {
div.dataTables_info {
white-space: unset;
}
}
//搜尋框
div.dataTables_wrapper {
div.dataTables_filter {
@include media-breakpoint-down(sm) {
text-align: right;
}
}
}

37
resources/sass/admin/lib.scss vendored Normal file
View File

@ -0,0 +1,37 @@
/**
Fonts
*/
@import url('https://fonts.googleapis.com/css?family=Nunito');
/**
Variables
*/
@import 'variables';
/**
Bootstrap
*/
@import '~bootstrap/scss/bootstrap';
/**
Core UI
*/
@import "~@coreui/coreui/scss/coreui-standalone";
/**
CoreUI Icons
*/
@import "~@coreui/icons/scss/free";
/**
Flag Icons
*/
@import "~flag-icon-css/sass/flag-icon";
/**
Font Awesome
*/
@import "~font-awesome/scss/font-awesome";
/**
Simple Line Icons
*/
@import "~simple-line-icons/scss/simple-line-icons";
/**
DataTables
*/
@import "~datatables.net-bs4/css/dataTables.bootstrap4.min.css";
@import "~datatables.net-responsive-bs4/css/responsive.bootstrap4.min.css";

View File

@ -1 +1,8 @@
//
// Fonts
@import url('https://fonts.googleapis.com/css?family=Nunito');
// Variables
@import 'variables';
// Bootstrap
@import '~bootstrap/scss/bootstrap';

View File

@ -0,0 +1,3 @@
@extends('admin.layouts.app')
@section('title', 'Admin Area')

View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
@stack('admin-app-head-scripts')
@section('admin-app-styles')
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600" rel="stylesheet" type="text/css">
<link href="{{ asset('css/admin/lib.css') }}" rel="stylesheet">
<link href="{{ asset('css/admin/app.css') }}" rel="stylesheet">
@show
@stack('admin-app-styles')
</head>
<body class="app admin-area sidebar-lg-show header-fixed sidebar-fixed aside-menu-fixed footer-fixed">
<header id="app-header" class="app-header navbar">
<button class="navbar-toggler sidebar-toggler d-lg-none mr-auto" type="button" data-toggle="sidebar-show">
<span class="navbar-toggler-icon"></span>
</button>
<button class="navbar-toggler sidebar-toggler d-md-down-none" type="button" data-toggle="sidebar-lg-show">
<span class="navbar-toggler-icon"></span>
</button>
<ul class="nav navbar-nav">
<li class="nav-item px-3">
<a href="{{ route('index') }}" class="nav-link">Front Stage</a>
</li>
</ul>
<ul class="nav navbar-nav ml-auto">
</ul>
<button class="navbar-toggler aside-menu-toggler d-md-down-none" type="button" data-toggle="aside-menu-lg-show">
<span class="navbar-toggler-icon"></span>
</button>
<button class="navbar-toggler aside-menu-toggler d-lg-none" type="button" data-toggle="aside-menu-show">
<span class="navbar-toggler-icon"></span>
</button>
<form action="{{ route('logout') }}" method="post">
@csrf
<button class="btn btn-outline-danger btn-sm">登出</button>
</form>
</header>
<div id="app-body" class="app-body">
{{-- 左側主選單--}}
<div class="sidebar">
<nav class="sidebar-nav">
<ul class="nav">
</ul>
</nav>
<button class="sidebar-minimizer brand-minimizer" type="button"></button>
</div>
{{-- 主頁面 --}}
<main class="main">
<div class="container-fluid p-2 p-sm-3 p-md-3">
@yield('admin-page-content')
</div>
</main>
{{-- 右側選單 --}}
<aside class="aside-menu">
</aside>
</div>
<footer id="app-footer" class="app-footer">
</footer>
@section('admin-app-scripts')
<script src="{{ asset('js/admin/app.js') }}"></script>
@show
@stack('admin-app-scripts')
</body>
</html>

View File

@ -0,0 +1,73 @@
@extends('layouts.app')
@section('content-body')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Login') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('login') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<div class="col-md-6 offset-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="form-check-label" for="remember">
{{ __('Remember Me') }}
</label>
</div>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Login') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,49 @@
@extends('layouts.app')
@section('content-body')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Confirm Password') }}</div>
<div class="card-body">
{{ __('Please confirm your password before continuing.') }}
<form method="POST" action="{{ route('password.confirm') }}">
@csrf
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Confirm Password') }}
</button>
@if (Route::has('password.request'))
<a class="btn btn-link" href="{{ route('password.request') }}">
{{ __('Forgot Your Password?') }}
</a>
@endif
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,47 @@
@extends('layouts.app')
@section('content-body')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
<form method="POST" action="{{ route('password.email') }}">
@csrf
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Send Password Reset Link') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,65 @@
@extends('layouts.app')
@section('content-body')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Reset Password') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('password.update') }}">
@csrf
<input type="hidden" name="token" value="{{ $token }}">
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ $email ?? old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Reset Password') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,77 @@
@extends('layouts.app')
@section('content-body')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Register') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('register') }}">
@csrf
<div class="form-group row">
<label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>
<div class="col-md-6">
<input id="name" type="text" class="form-control @error('name') is-invalid @enderror" name="name" value="{{ old('name') }}" required autocomplete="name" autofocus>
@error('name')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email">
@error('email')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
<strong>{{ $message }}</strong>
</span>
@enderror
</div>
</div>
<div class="form-group row">
<label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Register') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,28 @@
@extends('layouts.app')
@section('content-body')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Verify Your Email Address') }}</div>
<div class="card-body">
@if (session('resent'))
<div class="alert alert-success" role="alert">
{{ __('A fresh verification link has been sent to your email address.') }}
</div>
@endif
{{ __('Before proceeding, please check your email for a verification link.') }}
{{ __('If you did not receive the email') }},
<form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
@csrf
<button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>.
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,50 @@
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav mr-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
<!-- Authentication Links -->
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>

View File

@ -1 +1,23 @@
@extends('layouts.app')
@section('content-body')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">Dashboard</div>
<div class="card-body">
@if (session('status'))
<div class="alert alert-success" role="alert">
{{ session('status') }}
</div>
@endif
You are logged in!
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -19,8 +19,13 @@
</head>
<body>
@section('content-header')
@include('components.nav')
@show
@yield('content-body')
<main>
@yield('content-body')
</main>
@section('content-footer')
@show

View File

@ -11,6 +11,20 @@
|
*/
Route::get('/', function () {
return view('home');
/**
* Authenticate routes
*/
Auth::routes(['verify' => true]);
/**
* Static page routes
*/
Route::view('/', 'home')->name('index');
/**
* Admin routes
*/
Route::group(['prefix' => config('admin.route'), 'middleware' => ['admin.area'], 'as' => config('admin.route_name_prefix')], function() {
Route::get('/', 'AdminPageController@index')->name('index');
});

15
webpack.mix.js vendored
View File

@ -11,5 +11,16 @@ const mix = require('laravel-mix');
|
*/
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
let publicJsDir = 'public/js',
publicCssDir = 'public/css',
publicAdminJsDir = 'public/js/admin',
publicAdminCssDir = 'public/css/admin'
mix.js('resources/js/app.js', publicJsDir)
mix.sass('resources/sass/app.scss', publicCssDir);
mix.js('resources/js/admin/app.js', publicAdminJsDir)
mix.sass('resources/sass/admin/lib.scss', publicAdminCssDir)
.sass('resources/sass/admin/app.scss', publicAdminCssDir)