Merge branch 'master' into pr/21
This commit is contained in:
commit
e378e1c63a
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use League\Glide\Server;
|
||||
|
||||
class ImagesController extends Controller
|
||||
{
|
||||
public function show(Server $glide)
|
||||
{
|
||||
return $glide->fromRequest()->response();
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ class UsersController extends Controller
|
|||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
'owner' => $user->owner,
|
||||
'photo' => $user->photoUrl(['w' => 40, 'h' => 40, 'fit' => 'crop']),
|
||||
'deleted_at' => $user->deleted_at,
|
||||
];
|
||||
}),
|
||||
|
@ -38,15 +39,23 @@ class UsersController extends Controller
|
|||
|
||||
public function store()
|
||||
{
|
||||
Auth::user()->account->users()->create(
|
||||
Request::validate([
|
||||
'first_name' => ['required', 'max:50'],
|
||||
'last_name' => ['required', 'max:50'],
|
||||
'email' => ['required', 'max:50', 'email', Rule::unique('users')],
|
||||
'password' => ['nullable'],
|
||||
'owner' => ['required', 'boolean'],
|
||||
])
|
||||
);
|
||||
Request::validate([
|
||||
'first_name' => ['required', 'max:50'],
|
||||
'last_name' => ['required', 'max:50'],
|
||||
'email' => ['required', 'max:50', 'email', Rule::unique('users')],
|
||||
'password' => ['nullable'],
|
||||
'owner' => ['required', 'boolean'],
|
||||
'photo' => ['nullable', 'image'],
|
||||
]);
|
||||
|
||||
Auth::user()->account->users()->create([
|
||||
'first_name' => Request::get('first_name'),
|
||||
'last_name' => Request::get('last_name'),
|
||||
'email' => Request::get('email'),
|
||||
'password' => Request::get('password'),
|
||||
'owner' => Request::get('owner'),
|
||||
'photo_path' => Request::file('photo') ? Request::file('photo')->store('users') : null,
|
||||
]);
|
||||
|
||||
return Redirect::route('users')->with('success', 'User created.');
|
||||
}
|
||||
|
@ -60,6 +69,7 @@ class UsersController extends Controller
|
|||
'last_name' => $user->last_name,
|
||||
'email' => $user->email,
|
||||
'owner' => $user->owner,
|
||||
'photo' => $user->photoUrl(['w' => 60, 'h' => 60, 'fit' => 'crop']),
|
||||
'deleted_at' => $user->deleted_at,
|
||||
],
|
||||
]);
|
||||
|
@ -73,10 +83,15 @@ class UsersController extends Controller
|
|||
'email' => ['required', 'max:50', 'email', Rule::unique('users')->ignore($user->id)],
|
||||
'password' => ['nullable'],
|
||||
'owner' => ['required', 'boolean'],
|
||||
'photo' => ['nullable', 'image'],
|
||||
]);
|
||||
|
||||
$user->update(Request::only('first_name', 'last_name', 'email', 'owner'));
|
||||
|
||||
if (Request::file('photo')) {
|
||||
$user->update(['photo_path' => Request::file('photo')->store('users')]);
|
||||
}
|
||||
|
||||
if (Request::get('password')) {
|
||||
$user->update(['password' => Request::get('password')]);
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ class Kernel extends HttpKernel
|
|||
*/
|
||||
protected $middlewareGroups = [
|
||||
'web' => [
|
||||
\Inertia\Middleware::class,
|
||||
\App\Http\Middleware\EncryptCookies::class,
|
||||
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
|
||||
\Illuminate\Session\Middleware\StartSession::class,
|
||||
|
|
|
@ -18,7 +18,7 @@ class RedirectIfAuthenticated
|
|||
public function handle($request, Closure $next, $guard = null)
|
||||
{
|
||||
if (Auth::guard($guard)->check()) {
|
||||
return redirect('/home');
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
|
|
|
@ -3,14 +3,15 @@
|
|||
namespace App\Providers;
|
||||
|
||||
use Inertia\Inertia;
|
||||
use League\Glide\Server;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Pagination\UrlWindow;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Date;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Request;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
|
||||
|
@ -22,17 +23,21 @@ class AppServiceProvider extends ServiceProvider
|
|||
}
|
||||
|
||||
public function register()
|
||||
{
|
||||
$this->registerInertia();
|
||||
$this->registerGlide();
|
||||
$this->registerLengthAwarePaginator();
|
||||
}
|
||||
|
||||
public function registerInertia()
|
||||
{
|
||||
Inertia::version(function () {
|
||||
return md5_file(public_path('mix-manifest.json'));
|
||||
});
|
||||
|
||||
Inertia::share(function () {
|
||||
return [
|
||||
'app' => [
|
||||
'name' => Config::get('app.name'),
|
||||
],
|
||||
'auth' => [
|
||||
Inertia::share([
|
||||
'auth' => function () {
|
||||
return [
|
||||
'user' => Auth::user() ? [
|
||||
'id' => Auth::user()->id,
|
||||
'first_name' => Auth::user()->first_name,
|
||||
|
@ -44,15 +49,31 @@ class AppServiceProvider extends ServiceProvider
|
|||
'name' => Auth::user()->account->name,
|
||||
],
|
||||
] : null,
|
||||
],
|
||||
'flash' => [
|
||||
];
|
||||
},
|
||||
'flash' => function () {
|
||||
return [
|
||||
'success' => Session::get('success'),
|
||||
],
|
||||
'errors' => Session::get('errors') ? Session::get('errors')->getBag('default')->getMessages() : (object) [],
|
||||
];
|
||||
});
|
||||
];
|
||||
},
|
||||
'errors' => function () {
|
||||
return Session::get('errors')
|
||||
? Session::get('errors')->getBag('default')->getMessages()
|
||||
: (object) [];
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
$this->registerLengthAwarePaginator();
|
||||
protected function registerGlide()
|
||||
{
|
||||
$this->app->bind(Server::class, function ($app) {
|
||||
return Server::create([
|
||||
'source' => Storage::getDriver(),
|
||||
'cache' => Storage::getDriver(),
|
||||
'cache_folder' => '.glide-cache',
|
||||
'base_url' => 'img',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
protected function registerLengthAwarePaginator()
|
||||
|
|
10
app/User.php
10
app/User.php
|
@ -2,6 +2,9 @@
|
|||
|
||||
namespace App;
|
||||
|
||||
use League\Glide\Server;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
@ -32,6 +35,13 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
|
|||
$this->attributes['password'] = Hash::make($password);
|
||||
}
|
||||
|
||||
public function photoUrl(array $attributes)
|
||||
{
|
||||
if ($this->photo_path) {
|
||||
return URL::to(App::make(Server::class)->fromPath($this->photo_path, $attributes));
|
||||
}
|
||||
}
|
||||
|
||||
public function scopeOrderByName($query)
|
||||
{
|
||||
$query->orderBy('last_name')->orderBy('first_name');
|
||||
|
|
|
@ -5,22 +5,23 @@
|
|||
"license": "MIT",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.1.3",
|
||||
"php": "^7.2",
|
||||
"fideloper/proxy": "^4.0",
|
||||
"fzaninotto/faker": "^1.4",
|
||||
"inertiajs/inertia-laravel": "dev-master",
|
||||
"laravel/framework": "5.8.*",
|
||||
"inertiajs/inertia-laravel": "^0.1",
|
||||
"laravel/framework": "^6.0",
|
||||
"laravel/tinker": "^1.0",
|
||||
"league/glide": "2.0.x-dev",
|
||||
"reinink/remember-query-strings": "^0.1.0",
|
||||
"tightenco/ziggy": "^0.6.9"
|
||||
"tightenco/ziggy": "^0.8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"barryvdh/laravel-debugbar": "^3.2",
|
||||
"beyondcode/laravel-dump-server": "^1.0",
|
||||
"filp/whoops": "^2.0",
|
||||
"facade/ignition": "^1.4",
|
||||
"fzaninotto/faker": "^1.4",
|
||||
"mockery/mockery": "^1.0",
|
||||
"nunomaduro/collision": "^2.0",
|
||||
"phpunit/phpunit": "^7.0"
|
||||
"nunomaduro/collision": "^3.0",
|
||||
"phpunit/phpunit": "^8.0"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
use Faker\Generator as Faker;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -19,7 +20,7 @@ $factory->define(App\User::class, function (Faker $faker) {
|
|||
'last_name' => $faker->lastName,
|
||||
'email' => $faker->unique()->safeEmail,
|
||||
'password' => 'secret',
|
||||
'remember_token' => str_random(10),
|
||||
'remember_token' => Str::random(10),
|
||||
'owner' => false,
|
||||
];
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ class CreateUsersTable extends Migration
|
|||
$table->string('email', 50)->unique();
|
||||
$table->string('password')->nullable();
|
||||
$table->boolean('owner')->default(false);
|
||||
$table->string('photo_path', 100)->nullable();
|
||||
$table->rememberToken();
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -11,13 +11,14 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@inertiajs/inertia": "^0.1.0",
|
||||
"@inertiajs/inertia-vue": "^0.1.0",
|
||||
"autosize": "^4.0.2",
|
||||
"axios": "^0.18",
|
||||
"cross-env": "^5.1",
|
||||
"eslint": "^5.14.1",
|
||||
"eslint-plugin-vue": "^5.2.2",
|
||||
"fuse.js": "^3.4.2",
|
||||
"inertia-vue": "inertiajs/inertia-vue",
|
||||
"laravel-mix": "^4.0.7",
|
||||
"lodash": "^4.17.5",
|
||||
"popper.js": "^1.12",
|
||||
|
@ -28,5 +29,8 @@
|
|||
"tailwindcss": "^1.0.1",
|
||||
"vue": "^2.6.6",
|
||||
"vue-template-compiler": "^2.6.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue-meta": "^2.2.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ composer install
|
|||
Install NPM dependencies:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm ci
|
||||
```
|
||||
|
||||
Build assets:
|
||||
|
|
|
@ -28,6 +28,7 @@ import Logo from '@/Shared/Logo'
|
|||
import TextInput from '@/Shared/TextInput'
|
||||
|
||||
export default {
|
||||
metaInfo: { title: 'Login' },
|
||||
components: {
|
||||
LoadingButton,
|
||||
Logo,
|
||||
|
@ -46,9 +47,6 @@ export default {
|
|||
},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.title = `Login | ${this.$page.app.name}`
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.sending = true
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<layout title="Create Contact">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">
|
||||
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('contacts')">Contacts</inertia-link>
|
||||
<span class="text-indigo-400 font-medium">/</span> Create
|
||||
|
@ -30,7 +30,7 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -40,8 +40,9 @@ import SelectInput from '@/Shared/SelectInput'
|
|||
import TextInput from '@/Shared/TextInput'
|
||||
|
||||
export default {
|
||||
metaInfo: { title: 'Create Contact' },
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
components: {
|
||||
Layout,
|
||||
LoadingButton,
|
||||
SelectInput,
|
||||
TextInput,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<layout :title="`${form.first_name} ${form.last_name}`">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">
|
||||
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('contacts')">Contacts</inertia-link>
|
||||
<span class="text-indigo-400 font-medium">/</span>
|
||||
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -46,8 +46,13 @@ import TextInput from '@/Shared/TextInput'
|
|||
import TrashedMessage from '@/Shared/TrashedMessage'
|
||||
|
||||
export default {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: `${this.form.first_name} ${this.form.last_name}`,
|
||||
}
|
||||
},
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
components: {
|
||||
Layout,
|
||||
LoadingButton,
|
||||
SelectInput,
|
||||
TextInput,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<layout title="Contacts">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">Contacts</h1>
|
||||
<div class="mb-6 flex justify-between items-center">
|
||||
<search-filter v-model="form.search" class="w-full max-w-md mr-4" @reset="reset">
|
||||
|
@ -59,7 +59,7 @@
|
|||
</table>
|
||||
</div>
|
||||
<pagination :links="contacts.links" />
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -70,9 +70,10 @@ import Pagination from '@/Shared/Pagination'
|
|||
import SearchFilter from '@/Shared/SearchFilter'
|
||||
|
||||
export default {
|
||||
metaInfo: { title: 'Contacts' },
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
components: {
|
||||
Icon,
|
||||
Layout,
|
||||
Pagination,
|
||||
SearchFilter,
|
||||
},
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
<template>
|
||||
<layout title="Dashboard">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">Dashboard</h1>
|
||||
<p class="mb-12 leading-normal">Hey there! Welcome to Ping CRM, a demo app designed to help illustrate how <a class="text-indigo-500 underline hover:text-orange-600" href="https://github.com/inertiajs">Inertia.js</a> works.</p>
|
||||
<div>
|
||||
<inertia-link class="btn-indigo-500" href="/500">500 error</inertia-link>
|
||||
<inertia-link class="btn-indigo-500" href="/404">404 error</inertia-link>
|
||||
</div>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Layout from '@/Shared/Layout'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Layout,
|
||||
},
|
||||
metaInfo: { title: 'Dashboard' },
|
||||
layout: Layout,
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<layout title="Create Organization">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">
|
||||
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('organizations')">Organizations</inertia-link>
|
||||
<span class="text-indigo-400 font-medium">/</span> Create
|
||||
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -35,8 +35,9 @@ import SelectInput from '@/Shared/SelectInput'
|
|||
import TextInput from '@/Shared/TextInput'
|
||||
|
||||
export default {
|
||||
metaInfo: { title: 'Create Organization' },
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
components: {
|
||||
Layout,
|
||||
LoadingButton,
|
||||
SelectInput,
|
||||
TextInput,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<layout :title="form.name">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">
|
||||
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('organizations')">Organizations</inertia-link>
|
||||
<span class="text-indigo-400 font-medium">/</span>
|
||||
|
@ -66,7 +66,7 @@
|
|||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -78,9 +78,12 @@ import TextInput from '@/Shared/TextInput'
|
|||
import TrashedMessage from '@/Shared/TrashedMessage'
|
||||
|
||||
export default {
|
||||
metaInfo() {
|
||||
return { title: this.form.name }
|
||||
},
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
components: {
|
||||
Icon,
|
||||
Layout,
|
||||
LoadingButton,
|
||||
SelectInput,
|
||||
TextInput,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<layout title="Organizations">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">Organizations</h1>
|
||||
<div class="mb-6 flex justify-between items-center">
|
||||
<search-filter v-model="form.search" class="w-full max-w-md mr-4" @reset="reset">
|
||||
|
@ -51,7 +51,7 @@
|
|||
</table>
|
||||
</div>
|
||||
<pagination :links="organizations.links" />
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -62,9 +62,10 @@ import Pagination from '@/Shared/Pagination'
|
|||
import SearchFilter from '@/Shared/SearchFilter'
|
||||
|
||||
export default {
|
||||
metaInfo: { title: 'Organizations' },
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
components: {
|
||||
Icon,
|
||||
Layout,
|
||||
Pagination,
|
||||
SearchFilter,
|
||||
},
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
<template>
|
||||
<layout title="Reports">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">Reports</h1>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Layout from '@/Shared/Layout'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Layout,
|
||||
},
|
||||
metaInfo: { title: 'Reports' },
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<layout title="Create User">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">
|
||||
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('users')">Users</inertia-link>
|
||||
<span class="text-indigo-400 font-medium">/</span> Create
|
||||
|
@ -15,13 +15,14 @@
|
|||
<option :value="true">Yes</option>
|
||||
<option :value="false">No</option>
|
||||
</select-input>
|
||||
<file-input v-model="form.photo" :errors="$page.errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" />
|
||||
</div>
|
||||
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex justify-end items-center">
|
||||
<loading-button :loading="sending" class="btn-indigo-500" type="submit">Create User</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -29,13 +30,16 @@ import Layout from '@/Shared/Layout'
|
|||
import LoadingButton from '@/Shared/LoadingButton'
|
||||
import SelectInput from '@/Shared/SelectInput'
|
||||
import TextInput from '@/Shared/TextInput'
|
||||
import FileInput from '@/Shared/FileInput'
|
||||
|
||||
export default {
|
||||
metaInfo: { title: 'Create User' },
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
components: {
|
||||
Layout,
|
||||
LoadingButton,
|
||||
SelectInput,
|
||||
TextInput,
|
||||
FileInput,
|
||||
},
|
||||
remember: 'form',
|
||||
data() {
|
||||
|
@ -47,13 +51,23 @@ export default {
|
|||
email: null,
|
||||
password: null,
|
||||
owner: false,
|
||||
photo: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.sending = true
|
||||
this.$inertia.post(this.route('users.store'), this.form)
|
||||
|
||||
var data = new FormData()
|
||||
data.append('first_name', this.form.first_name || '')
|
||||
data.append('last_name', this.form.last_name || '')
|
||||
data.append('email', this.form.email || '')
|
||||
data.append('password', this.form.password || '')
|
||||
data.append('owner', this.form.owner ? '1' : '0')
|
||||
data.append('photo', this.form.photo || '')
|
||||
|
||||
this.$inertia.post(this.route('users.store'), data)
|
||||
.then(() => this.sending = false)
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
<template>
|
||||
<layout :title="`${form.first_name} ${form.last_name}`">
|
||||
<h1 class="mb-8 font-bold text-3xl">
|
||||
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('users')">Users</inertia-link>
|
||||
<span class="text-indigo-400 font-medium">/</span>
|
||||
{{ form.first_name }} {{ form.last_name }}
|
||||
</h1>
|
||||
<div>
|
||||
<div class="mb-8 flex justify-start max-w-lg">
|
||||
<h1 class="font-bold text-3xl">
|
||||
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('users')">Users</inertia-link>
|
||||
<span class="text-indigo-400 font-medium">/</span>
|
||||
{{ form.first_name }} {{ form.last_name }}
|
||||
</h1>
|
||||
<img v-if="user.photo" class="block w-8 h-8 rounded-full ml-4" :src="user.photo">
|
||||
</div>
|
||||
<trashed-message v-if="user.deleted_at" class="mb-6" @restore="restore">
|
||||
This user has been deleted.
|
||||
</trashed-message>
|
||||
<div class="bg-white rounded shadow overflow-hidden max-w-3xl">
|
||||
<div class="bg-white rounded shadow overflow-hidden max-w-lg">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="p-8 -mr-6 -mb-8 flex flex-wrap">
|
||||
<text-input v-model="form.first_name" :errors="$page.errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
|
||||
|
@ -19,14 +22,15 @@
|
|||
<option :value="true">Yes</option>
|
||||
<option :value="false">No</option>
|
||||
</select-input>
|
||||
<file-input v-model="form.photo" :errors="$page.errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" />
|
||||
</div>
|
||||
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex items-center">
|
||||
<button v-if="!user.deleted_at" class="text-red-700 hover:underline" tabindex="-1" type="button" @click="destroy">Delete User</button>
|
||||
<loading-button :loading="sending" class="btn-indigo-500 ml-auto" type="submit">Update User</loading-button>
|
||||
<div class="px-8 py-4 bg-grey-lightest border-t border-grey-lighter flex items-center">
|
||||
<button v-if="!user.deleted_at" class="text-red hover:underline" tabindex="-1" type="button" @click="destroy">Delete User</button>
|
||||
<loading-button :loading="sending" class="btn-indigo ml-auto" type="submit">Update User</loading-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -34,14 +38,21 @@ import Layout from '@/Shared/Layout'
|
|||
import LoadingButton from '@/Shared/LoadingButton'
|
||||
import SelectInput from '@/Shared/SelectInput'
|
||||
import TextInput from '@/Shared/TextInput'
|
||||
import FileInput from '@/Shared/FileInput'
|
||||
import TrashedMessage from '@/Shared/TrashedMessage'
|
||||
|
||||
export default {
|
||||
metaInfo() {
|
||||
return {
|
||||
title: `${this.form.first_name} ${this.form.last_name}`,
|
||||
}
|
||||
},
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
components: {
|
||||
Layout,
|
||||
LoadingButton,
|
||||
SelectInput,
|
||||
TextInput,
|
||||
FileInput,
|
||||
TrashedMessage,
|
||||
},
|
||||
props: {
|
||||
|
@ -57,14 +68,31 @@ export default {
|
|||
email: this.user.email,
|
||||
password: this.user.password,
|
||||
owner: this.user.owner,
|
||||
photo: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.sending = true
|
||||
this.$inertia.put(this.route('users.update', this.user.id), this.form)
|
||||
.then(() => this.sending = false)
|
||||
|
||||
var data = new FormData()
|
||||
data.append('first_name', this.form.first_name || '')
|
||||
data.append('last_name', this.form.last_name || '')
|
||||
data.append('email', this.form.email || '')
|
||||
data.append('password', this.form.password || '')
|
||||
data.append('owner', this.form.owner ? '1' : '0')
|
||||
data.append('photo', this.form.photo || '')
|
||||
data.append('_method', 'put')
|
||||
|
||||
this.$inertia.post(this.route('users.update', this.user.id), data)
|
||||
.then(() => {
|
||||
this.sending = false
|
||||
if (Object.keys(this.$page.errors).length === 0) {
|
||||
this.form.photo = null
|
||||
this.form.password = null
|
||||
}
|
||||
})
|
||||
},
|
||||
destroy() {
|
||||
if (confirm('Are you sure you want to delete this user?')) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<layout title="Users">
|
||||
<div>
|
||||
<h1 class="mb-8 font-bold text-3xl">Users</h1>
|
||||
<div class="mb-6 flex justify-between items-center">
|
||||
<search-filter v-model="form.search" class="w-full max-w-md mr-4" @reset="reset">
|
||||
|
@ -31,6 +31,7 @@
|
|||
<tr v-for="user in users" :key="user.id" class="hover:bg-gray-100 focus-within:bg-gray-100">
|
||||
<td class="border-t">
|
||||
<inertia-link class="px-6 py-4 flex items-center focus:text-indigo-500" :href="route('users.edit', user.id)">
|
||||
<img v-if="user.photo" class="block w-5 h-5 rounded-full mr-2 -my-2" :src="user.photo">
|
||||
{{ user.name }}
|
||||
<icon v-if="user.deleted_at" name="trash" class="flex-shrink-0 w-3 h-3 fill-gray-400 ml-2" />
|
||||
</inertia-link>
|
||||
|
@ -56,7 +57,7 @@
|
|||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</layout>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -66,9 +67,10 @@ import Layout from '@/Shared/Layout'
|
|||
import SearchFilter from '@/Shared/SearchFilter'
|
||||
|
||||
export default {
|
||||
metaInfo: { title: 'Users' },
|
||||
layout: (h, page) => h(Layout, [page]),
|
||||
components: {
|
||||
Icon,
|
||||
Layout,
|
||||
SearchFilter,
|
||||
},
|
||||
props: {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<div>
|
||||
<label v-if="label" class="form-label">{{ label }}:</label>
|
||||
<div class="form-input p-0" :class="{ error: errors.length }">
|
||||
<input ref="file" type="file" :accept="accept" class="hidden" @change="change">
|
||||
<div v-if="!value" class="p-2">
|
||||
<button type="button" class="px-4 py-1 bg-grey-dark hover:bg-grey-darker rounded-sm text-xs font-medium text-white" @click="browse">
|
||||
Browse
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-between p-2">
|
||||
<div class="flex-1 pr-1">{{ value.name }} <span class="text-grey-dark text-xs">({{ filesize(value.size) }})</span></div>
|
||||
<button type="button" class="px-4 py-1 bg-grey-dark hover:bg-grey-darker rounded-sm text-xs font-medium text-white" @click="remove">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="errors.length" class="form-error">{{ errors[0] }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: File,
|
||||
label: String,
|
||||
accept: String,
|
||||
errors: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(value) {
|
||||
if (!value) {
|
||||
this.$refs.file.value = ''
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
filesize(size) {
|
||||
var i = Math.floor(Math.log(size) / Math.log(1024))
|
||||
return (size / Math.pow(1024, i) ).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]
|
||||
},
|
||||
browse() {
|
||||
this.$refs.file.click()
|
||||
},
|
||||
change(e) {
|
||||
this.$emit('input', e.target.files[0])
|
||||
},
|
||||
remove() {
|
||||
this.$emit('input', null)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<portal-target name="dropdown" slim />
|
||||
<div class="flex flex-col">
|
||||
<div class="min-h-screen flex flex-col" @click="hideDropdownMenus">
|
||||
<div class="h-screen flex flex-col" @click="hideDropdownMenus">
|
||||
<div class="md:flex">
|
||||
<div class="bg-indigo-900 md:flex-shrink-0 md:w-56 px-6 py-4 flex items-center justify-between md:justify-center">
|
||||
<inertia-link class="mt-1" href="/">
|
||||
|
@ -10,8 +10,8 @@
|
|||
</inertia-link>
|
||||
<dropdown class="md:hidden" placement="bottom-end">
|
||||
<svg class="fill-white w-6 h-6" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" /></svg>
|
||||
<div slot="dropdown" class="mt-2 px-8 py-4 shadow-xl bg-indigo-800 rounded">
|
||||
<main-menu />
|
||||
<div slot="dropdown" class="mt-2 px-8 py-4 shadow-lg bg-indigo-800 rounded">
|
||||
<main-menu :url="url()" />
|
||||
</div>
|
||||
</dropdown>
|
||||
</div>
|
||||
|
@ -33,11 +33,9 @@
|
|||
</dropdown>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-grow">
|
||||
<div class="bg-indigo-800 flex-shrink-0 w-56 p-12 hidden md:block">
|
||||
<main-menu />
|
||||
</div>
|
||||
<div class="w-full overflow-hidden px-4 py-8 md:p-12">
|
||||
<div class="flex flex-grow overflow-hidden">
|
||||
<main-menu :url="url()" class="bg-indigo-800 flex-no-shrink w-56 p-12 hidden md:block overflow-y-auto" />
|
||||
<div class="w-full overflow-hidden px-4 py-8 md:p-12 overflow-y-auto" scroll-region>
|
||||
<flash-messages />
|
||||
<slot />
|
||||
</div>
|
||||
|
@ -62,26 +60,15 @@ export default {
|
|||
Logo,
|
||||
MainMenu,
|
||||
},
|
||||
props: {
|
||||
title: String,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showUserMenu: false,
|
||||
accounts: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
title(title) {
|
||||
this.updatePageTitle(title)
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.updatePageTitle(this.title)
|
||||
},
|
||||
methods: {
|
||||
updatePageTitle(title) {
|
||||
document.title = title ? `${title} | ${this.$page.app.name}` : this.$page.app.name
|
||||
url() {
|
||||
return location.pathname.substr(1)
|
||||
},
|
||||
hideDropdownMenus() {
|
||||
this.showUserMenu = false
|
||||
|
|
|
@ -34,13 +34,16 @@ export default {
|
|||
components: {
|
||||
Icon,
|
||||
},
|
||||
props: {
|
||||
url: String,
|
||||
},
|
||||
methods: {
|
||||
isUrl(...urls) {
|
||||
if (urls[0] === '') {
|
||||
return location.pathname.substr(1) === ''
|
||||
return this.url === ''
|
||||
}
|
||||
|
||||
return urls.filter(url => location.pathname.substr(1).startsWith(url)).length
|
||||
return urls.filter(url => this.url.startsWith(url)).length
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
import Inertia from 'inertia-vue'
|
||||
import PortalVue from 'portal-vue'
|
||||
import Vue from 'vue'
|
||||
import VueMeta from 'vue-meta'
|
||||
import PortalVue from 'portal-vue'
|
||||
import { InertiaApp } from '@inertiajs/inertia-vue'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.mixin({ methods: { route: (...args) => window.route(...args).url() } })
|
||||
Vue.use(Inertia)
|
||||
Vue.mixin({ methods: { route: window.route } })
|
||||
Vue.use(InertiaApp)
|
||||
Vue.use(PortalVue)
|
||||
Vue.use(VueMeta)
|
||||
|
||||
let app = document.getElementById('app')
|
||||
|
||||
new Vue({
|
||||
render: h => h(Inertia, {
|
||||
metaInfo: {
|
||||
title: 'Loading…',
|
||||
titleTemplate: '%s | Ping CRM',
|
||||
},
|
||||
render: h => h(InertiaApp, {
|
||||
props: {
|
||||
initialPage: JSON.parse(app.dataset.page),
|
||||
resolveComponent: name => import(`@/Pages/${name}`).then(module => module.default),
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
*/
|
||||
|
||||
// Auth
|
||||
Route::get('login')->name('login')->uses('Auth\LoginController@showLoginForm');
|
||||
Route::post('login')->name('login.attempt')->uses('Auth\LoginController@login');
|
||||
Route::get('login')->name('login')->uses('Auth\LoginController@showLoginForm')->middleware('guest');
|
||||
Route::post('login')->name('login.attempt')->uses('Auth\LoginController@login')->middleware('guest');
|
||||
Route::post('logout')->name('logout')->uses('Auth\LoginController@logout');
|
||||
|
||||
// Dashboard
|
||||
|
@ -28,6 +28,9 @@ Route::put('users/{user}')->name('users.update')->uses('UsersController@update')
|
|||
Route::delete('users/{user}')->name('users.destroy')->uses('UsersController@destroy')->middleware('auth');
|
||||
Route::put('users/{user}/restore')->name('users.restore')->uses('UsersController@restore')->middleware('auth');
|
||||
|
||||
// Images
|
||||
Route::get('/img/{path}', 'ImagesController@show')->where('path', '.*');
|
||||
|
||||
// Organizations
|
||||
Route::get('organizations')->name('organizations')->uses('OrganizationsController@index')->middleware('remember', 'auth');
|
||||
Route::get('organizations/create')->name('organizations.create')->uses('OrganizationsController@create')->middleware('auth');
|
||||
|
|
Loading…
Reference in New Issue