Merge branch 'master' into pr/21

This commit is contained in:
Jonathan Reinink 2019-11-27 16:50:57 -05:00
commit e378e1c63a
30 changed files with 3433 additions and 1812 deletions

View File

@ -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();
}
}

View File

@ -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')]);
}

View File

@ -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,

View File

@ -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);

View File

@ -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()

View File

@ -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');

View File

@ -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": [

1971
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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,
];
});

View File

@ -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();

2866
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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"
}
}

View File

@ -22,7 +22,7 @@ composer install
Install NPM dependencies:
```sh
npm install
npm ci
```
Build assets:

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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,
},

View File

@ -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>

View File

@ -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,

View File

@ -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,

View File

@ -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,
},

View File

@ -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>

View File

@ -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)
},
},

View File

@ -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?')) {

View File

@ -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: {

View File

@ -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>

View File

@ -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

View File

@ -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
},
},
}

16
resources/js/app.js vendored
View File

@ -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),

View File

@ -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');