Merge pull request #99 from inertiajs/upgrade

Upgrade Ping CRM to latest version of Inertia.js
This commit is contained in:
Jonathan Reinink 2021-02-27 11:37:26 -05:00 committed by GitHub
commit 57593efb25
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 5612 additions and 4966 deletions

39
.eslintrc.js vendored
View file

@ -1,15 +1,32 @@
module.exports = { module.exports = {
extends: [ extends: ['eslint:recommended', 'plugin:vue/recommended'],
'eslint:recommended', parserOptions: {
'plugin:vue/recommended', ecmaVersion: 2020,
], sourceType: 'module',
},
env: {
amd: true,
browser: true,
es6: true,
},
rules: { rules: {
"indent": ['error', 2], indent: ['error', 2],
'quotes': ['warn', 'single'], quotes: ['warn', 'single'],
'semi': ['warn', 'never'], semi: ['warn', 'never'],
'no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }],
'comma-dangle': ['warn', 'always-multiline'], 'comma-dangle': ['warn', 'always-multiline'],
'vue/max-attributes-per-line': false, 'vue/max-attributes-per-line': 'off',
'vue/require-default-prop': false, 'vue/require-default-prop': 'off',
'vue/singleline-html-element-content-newline': false, 'vue/singleline-html-element-content-newline': 'off',
} 'vue/html-self-closing': [
'warn',
{
html: {
void: 'always',
normal: 'always',
component: 'always',
},
},
],
},
} }

8
.prettierrc Normal file
View file

@ -0,0 +1,8 @@
{
"printWidth": 10000,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"htmlWhitespaceSensitivity": "css"
}

View file

@ -20,7 +20,7 @@ class ContactsController extends Controller
->orderByName() ->orderByName()
->filter(Request::only('search', 'trashed')) ->filter(Request::only('search', 'trashed'))
->paginate() ->paginate()
->transform(function ($contact) { ->through(function ($contact) {
return [ return [
'id' => $contact->id, 'id' => $contact->id,
'name' => $contact->name, 'name' => $contact->name,

View file

@ -18,7 +18,15 @@ class OrganizationsController extends Controller
->orderBy('name') ->orderBy('name')
->filter(Request::only('search', 'trashed')) ->filter(Request::only('search', 'trashed'))
->paginate() ->paginate()
->only('id', 'name', 'phone', 'city', 'deleted_at'), ->through(function ($organization) {
return [
'id' => $organization->id,
'name' => $organization->name,
'phone' => $organization->phone,
'city' => $organization->city,
'deleted_at' => $organization->deleted_at,
];
}),
]); ]);
} }

View file

@ -44,7 +44,7 @@ class HandleInertiaRequests extends Middleware
'first_name' => $request->user()->first_name, 'first_name' => $request->user()->first_name,
'last_name' => $request->user()->last_name, 'last_name' => $request->user()->last_name,
'email' => $request->user()->email, 'email' => $request->user()->email,
'role' => $request->user()->role, 'owner' => $request->user()->owner,
'account' => [ 'account' => [
'id' => $request->user()->account->id, 'id' => $request->user()->account->id,
'name' => $request->user()->account->name, 'name' => $request->user()->account->name,

View file

@ -2,10 +2,6 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\UrlWindow;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use League\Glide\Server; use League\Glide\Server;
@ -18,12 +14,6 @@ class AppServiceProvider extends ServiceProvider
* @return void * @return void
*/ */
public function register() public function register()
{
$this->registerGlide();
$this->registerLengthAwarePaginator();
}
protected function registerGlide()
{ {
$this->app->bind(Server::class, function ($app) { $this->app->bind(Server::class, function ($app) {
return Server::create([ return Server::create([
@ -35,75 +25,13 @@ class AppServiceProvider extends ServiceProvider
}); });
} }
protected function registerLengthAwarePaginator() /**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{ {
$this->app->bind(LengthAwarePaginator::class, function ($app, $values) { //
return new class(...array_values($values)) extends LengthAwarePaginator {
public function only(...$attributes)
{
return $this->transform(function ($item) use ($attributes) {
return $item->only($attributes);
});
}
public function transform($callback)
{
$this->items->transform($callback);
return $this;
}
public function toArray()
{
return [
'data' => $this->items->toArray(),
'links' => $this->links(),
];
}
public function links($view = null, $data = [])
{
$this->appends(Request::all());
$window = UrlWindow::make($this);
$elements = array_filter([
$window['first'],
is_array($window['slider']) ? '...' : null,
$window['slider'],
is_array($window['last']) ? '...' : null,
$window['last'],
]);
return Collection::make($elements)->flatMap(function ($item) {
if (is_array($item)) {
return Collection::make($item)->map(function ($url, $page) {
return [
'url' => $url,
'label' => $page,
'active' => $this->currentPage() === $page,
];
});
} else {
return [
[
'url' => null,
'label' => '...',
'active' => false,
],
];
}
})->prepend([
'url' => $this->previousPageUrl(),
'label' => 'Previous',
'active' => false,
])->push([
'url' => $this->nextPageUrl(),
'label' => 'Next',
'active' => false,
]);
}
};
});
} }
} }

View file

@ -8,7 +8,7 @@
], ],
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.3.0", "php": "^7.3|^8.0",
"ext-exif": "*", "ext-exif": "*",
"ext-gd": "*", "ext-gd": "*",
"facade/ignition": "^2.3.6", "facade/ignition": "^2.3.6",

1809
composer.lock generated

File diff suppressed because it is too large Load diff

7948
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,31 +2,31 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "npm run development", "dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", "development": "mix",
"watch": "npm run development -- --watch", "watch": "mix watch",
"watch-poll": "npm run watch -- --watch-poll", "watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", "hot": "mix watch --hot",
"prod": "npm run production", "prod": "npm run production",
"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" "production": "mix --production"
}, },
"dependencies": { "dependencies": {
"@fullhuman/postcss-purgecss": "^1.3.0", "@inertiajs/inertia": "^0.8.5",
"@inertiajs/inertia": "^0.3.1", "@inertiajs/inertia-vue": "^0.5.5",
"@inertiajs/inertia-vue": "^0.2.3", "@inertiajs/progress": "^0.2.4",
"@inertiajs/progress": "^0.1.0", "autoprefixer": "^10.2.4",
"cross-env": "^5.2.1", "eslint": "^6.8.0",
"eslint": "^5.16.0", "eslint-plugin-vue": "^7.5.0",
"eslint-plugin-vue": "^5.2.3", "laravel-mix": "^6.0.6",
"laravel-mix": "^5.0.1", "lodash": "^4.17.21",
"lodash": "^4.17.15",
"popper.js": "^1.16.0", "popper.js": "^1.16.0",
"portal-vue": "^1.5.1", "portal-vue": "^1.5.1",
"postcss": "^8.2.6",
"postcss-import": "^12.0.1", "postcss-import": "^12.0.1",
"postcss-nesting": "^7.0.1", "postcss-nesting": "^7.0.1",
"resolve-url-loader": "^2.3.2", "tailwindcss": "^2.0.3",
"tailwindcss": "^1.2.0-canary.5",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-loader": "^15.9.6",
"vue-meta": "^2.3.1", "vue-meta": "^2.3.1",
"vue-template-compiler": "^2.6.11" "vue-template-compiler": "^2.6.12"
} }
} }

12
resources/css/app.css vendored
View file

@ -1,11 +1,11 @@
/* Reset */ /* Reset */
@import "tailwindcss/base"; @import 'tailwindcss/base';
@import "reset"; @import 'reset';
/* Components */ /* Components */
@import "tailwindcss/components"; @import 'tailwindcss/components';
@import "buttons"; @import 'buttons';
@import "form"; @import 'form';
/* Utilities */ /* Utilities */
@import "tailwindcss/utilities"; @import 'tailwindcss/utilities';

View file

@ -1,7 +1,5 @@
.btn-indigo { .btn-indigo {
@apply px-6 py-3 rounded bg-indigo-600 text-white text-sm font-bold whitespace-no-wrap; @apply px-6 py-3 rounded bg-indigo-600 text-white text-sm leading-4 font-bold whitespace-nowrap hover:bg-orange-400 focus:bg-orange-400;
&:hover, &:focus { @apply bg-orange-500 }
} }
.btn-spinner, .btn-spinner,
@ -15,15 +13,19 @@
font-size: 10px; font-size: 10px;
position: relative; position: relative;
text-indent: -9999em; text-indent: -9999em;
border-top: .2em solid white; border-top: 0.2em solid white;
border-right: .2em solid white; border-right: 0.2em solid white;
border-bottom: .2em solid white; border-bottom: 0.2em solid white;
border-left: .2em solid transparent; border-left: 0.2em solid transparent;
transform: translateZ(0); transform: translateZ(0);
animation: spinning 1s infinite linear; animation: spinning 1s infinite linear;
} }
@keyframes spinning { @keyframes spinning {
0% { transform: rotate(0deg) } 0% {
100% { transform: rotate(360deg) } transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
} }

View file

@ -1,25 +1,19 @@
.form-label { .form-label {
@apply .mb-2 .block .text-gray-700 .select-none; @apply mb-2 block text-gray-700 select-none;
} }
.form-input, .form-input,
.form-textarea, .form-textarea,
.form-select { .form-select {
@apply .p-2 .leading-normal .block .w-full .border .text-gray-700 .bg-white .font-sans .rounded .text-left .appearance-none .relative; @apply p-2 leading-normal block w-full border text-gray-700 bg-white font-sans rounded text-left appearance-none relative focus:border-indigo-400 focus:ring;
&:focus,
&.focus {
@apply .border-indigo-500;
box-shadow: 0 0 0 1px theme('colors.indigo.500');
}
&::placeholder { &::placeholder {
@apply .text-gray-500 .opacity-100; @apply text-gray-500 opacity-100;
} }
} }
.form-select { .form-select {
@apply .pr-6; @apply pr-6;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAQCAYAAAAMJL+VAAAABGdBTUEAALGPC/xhBQAAAQtJREFUOBG1lEEOgjAQRalbGj2OG9caOACn4ALGtfEuHACiazceR1PWOH/CNA3aMiTaBDpt/7zPdBKy7M/DCL9pGkvxxVp7KsvyJftL5rZt1865M+Ucq6pyyF3hNcI7Cuu+728QYn/JQA5yKaempxuZmQngOwEaYx55nu+1lQh8GIatMGi+01NwBcEmhxBqK4nAPZJ78K0KKFAJmR3oPp8+Iwgob0Oa6+TLoeCvRx+mTUYf/FVBGTPRwDkfLxnaSrRwcH0FWhNOmrkWYbE2XEicqgSa1J0LQ+aPCuQgZiLnwewbGuz5MGoAhcIkCQcjaTBjMgtXGURMVHC1wcQEy0J+Zlj8bKAnY1/UzDe2dbAVqfXn6wAAAABJRU5ErkJggg=='); background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAQCAYAAAAMJL+VAAAABGdBTUEAALGPC/xhBQAAAQtJREFUOBG1lEEOgjAQRalbGj2OG9caOACn4ALGtfEuHACiazceR1PWOH/CNA3aMiTaBDpt/7zPdBKy7M/DCL9pGkvxxVp7KsvyJftL5rZt1865M+Ucq6pyyF3hNcI7Cuu+728QYn/JQA5yKaempxuZmQngOwEaYx55nu+1lQh8GIatMGi+01NwBcEmhxBqK4nAPZJ78K0KKFAJmR3oPp8+Iwgob0Oa6+TLoeCvRx+mTUYf/FVBGTPRwDkfLxnaSrRwcH0FWhNOmrkWYbE2XEicqgSa1J0LQ+aPCuQgZiLnwewbGuz5MGoAhcIkCQcjaTBjMgtXGURMVHC1wcQEy0J+Zlj8bKAnY1/UzDe2dbAVqfXn6wAAAABJRU5ErkJggg==');
background-size: 0.7rem; background-size: 0.7rem;
@ -27,20 +21,16 @@
background-position: right 0.7rem center; background-position: right 0.7rem center;
&::-ms-expand { &::-ms-expand {
@apply .opacity-0; @apply opacity-0;
} }
} }
.form-error {
@apply .text-red-700 .mt-2 .text-sm;
}
.form-input.error, .form-input.error,
.form-textarea.error, .form-textarea.error,
.form-select.error { .form-select.error {
@apply .border-red-600; @apply border-red-500 focus:ring focus:ring-red-200;
}
&:focus {
box-shadow: 0 0 0 1px theme('colors.red.600'); .form-error {
} @apply text-red-700 mt-2 text-sm;
} }

View file

@ -2,20 +2,20 @@
<div class="p-6 bg-indigo-800 min-h-screen flex justify-center items-center"> <div class="p-6 bg-indigo-800 min-h-screen flex justify-center items-center">
<div class="w-full max-w-md"> <div class="w-full max-w-md">
<logo class="block mx-auto w-full max-w-xs fill-white" height="50" /> <logo class="block mx-auto w-full max-w-xs fill-white" height="50" />
<form class="mt-8 bg-white rounded-lg shadow-xl overflow-hidden" @submit.prevent="submit"> <form class="mt-8 bg-white rounded-lg shadow-xl overflow-hidden" @submit.prevent="login">
<div class="px-10 py-12"> <div class="px-10 py-12">
<h1 class="text-center font-bold text-3xl">Welcome Back!</h1> <h1 class="text-center font-bold text-3xl">Welcome Back!</h1>
<div class="mx-auto mt-6 w-24 border-b-2" /> <div class="mx-auto mt-6 w-24 border-b-2" />
<text-input v-model="form.email" :error="errors.email" class="mt-10" label="Email" type="email" autofocus autocapitalize="off" /> <text-input v-model="form.email" :error="form.errors.email" class="mt-10" label="Email" type="email" autofocus autocapitalize="off" />
<text-input v-model="form.password" class="mt-6" label="Password" type="password" /> <text-input v-model="form.password" :error="form.errors.password" class="mt-6" label="Password" type="password" />
<label class="mt-6 select-none flex items-center" for="remember"> <label class="mt-6 select-none flex items-center" for="remember">
<input id="remember" v-model="form.remember" class="mr-1" type="checkbox"> <input id="remember" v-model="form.remember" class="mr-1" type="checkbox" />
<span class="text-sm">Remember Me</span> <span class="text-sm">Remember Me</span>
</label> </label>
</div> </div>
<div class="px-10 py-4 bg-gray-100 border-t border-gray-200 flex justify-between items-center"> <div class="px-10 py-4 bg-gray-100 border-t border-gray-100 flex justify-between items-center">
<a class="hover:underline" tabindex="-1" href="#reset-password">Forget password?</a> <a class="hover:underline" tabindex="-1" href="#reset-password">Forget password?</a>
<loading-button :loading="sending" class="btn-indigo" type="submit">Login</loading-button> <loading-button :loading="form.processing" class="btn-indigo" type="submit">Login</loading-button>
</div> </div>
</form> </form>
</div> </div>
@ -23,9 +23,9 @@
</template> </template>
<script> <script>
import LoadingButton from '@/Shared/LoadingButton'
import Logo from '@/Shared/Logo' import Logo from '@/Shared/Logo'
import TextInput from '@/Shared/TextInput' import TextInput from '@/Shared/TextInput'
import LoadingButton from '@/Shared/LoadingButton'
export default { export default {
metaInfo: { title: 'Login' }, metaInfo: { title: 'Login' },
@ -34,31 +34,23 @@ export default {
Logo, Logo,
TextInput, TextInput,
}, },
props: {
errors: Object,
},
data() { data() {
return { return {
sending: false, form: this.$inertia.form({
form: {
email: 'johndoe@example.com', email: 'johndoe@example.com',
password: 'secret', password: 'secret',
remember: null, remember: false,
}, }),
} }
}, },
methods: { methods: {
submit() { login() {
const data = { this.form
email: this.form.email, .transform(data => ({
password: this.form.password, ...data,
remember: this.form.remember, remember: data.remember ? 'on' : '',
} }))
.post(this.route('login.attempt'))
this.$inertia.post(this.route('login.attempt'), data, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
}, },
}, },
} }

View file

@ -4,29 +4,29 @@
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('contacts')">Contacts</inertia-link> <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 <span class="text-indigo-400 font-medium">/</span> Create
</h1> </h1>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl"> <div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit"> <form @submit.prevent="store">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap"> <div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.first_name" :error="errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" /> <text-input v-model="form.first_name" :error="form.errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" /> <text-input v-model="form.last_name" :error="form.errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<select-input v-model="form.organization_id" :error="errors.organization_id" class="pr-6 pb-8 w-full lg:w-1/2" label="Organization"> <select-input v-model="form.organization_id" :error="form.errors.organization_id" class="pr-6 pb-8 w-full lg:w-1/2" label="Organization">
<option :value="null" /> <option :value="null" />
<option v-for="organization in organizations" :key="organization.id" :value="organization.id">{{ organization.name }}</option> <option v-for="organization in organizations" :key="organization.id" :value="organization.id">{{ organization.name }}</option>
</select-input> </select-input>
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" /> <text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" /> <text-input v-model="form.phone" :error="form.errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" /> <text-input v-model="form.address" :error="form.errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" /> <text-input v-model="form.city" :error="form.errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" /> <text-input v-model="form.region" :error="form.errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country"> <select-input v-model="form.country" :error="form.errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<option :value="null" /> <option :value="null" />
<option value="CA">Canada</option> <option value="CA">Canada</option>
<option value="US">United States</option> <option value="US">United States</option>
</select-input> </select-input>
<text-input v-model="form.postal_code" :error="errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" /> <text-input v-model="form.postal_code" :error="form.errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
</div> </div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex justify-end items-center"> <div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex justify-end items-center">
<loading-button :loading="sending" class="btn-indigo" type="submit">Create Contact</loading-button> <loading-button :loading="form.processing" class="btn-indigo" type="submit">Create Contact</loading-button>
</div> </div>
</form> </form>
</div> </div>
@ -35,27 +35,25 @@
<script> <script>
import Layout from '@/Shared/Layout' import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput' import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
export default { export default {
metaInfo: { title: 'Create Contact' }, metaInfo: { title: 'Create Contact' },
layout: Layout,
components: { components: {
LoadingButton, LoadingButton,
SelectInput, SelectInput,
TextInput, TextInput,
}, },
layout: Layout,
props: { props: {
errors: Object,
organizations: Array, organizations: Array,
}, },
remember: 'form', remember: 'form',
data() { data() {
return { return {
sending: false, form: this.$inertia.form({
form: {
first_name: null, first_name: null,
last_name: null, last_name: null,
organization_id: null, organization_id: null,
@ -66,15 +64,12 @@ export default {
region: null, region: null,
country: null, country: null,
postal_code: null, postal_code: null,
}, }),
} }
}, },
methods: { methods: {
submit() { store() {
this.$inertia.post(this.route('contacts.store'), this.form, { this.form.post(this.route('contacts.store'))
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
}, },
}, },
} }

View file

@ -8,30 +8,30 @@
<trashed-message v-if="contact.deleted_at" class="mb-6" @restore="restore"> <trashed-message v-if="contact.deleted_at" class="mb-6" @restore="restore">
This contact has been deleted. This contact has been deleted.
</trashed-message> </trashed-message>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl"> <div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit"> <form @submit.prevent="update">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap"> <div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.first_name" :error="errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" /> <text-input v-model="form.first_name" :error="form.errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" /> <text-input v-model="form.last_name" :error="form.errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<select-input v-model="form.organization_id" :error="errors.organization_id" class="pr-6 pb-8 w-full lg:w-1/2" label="Organization"> <select-input v-model="form.organization_id" :error="form.errors.organization_id" class="pr-6 pb-8 w-full lg:w-1/2" label="Organization">
<option :value="null" /> <option :value="null" />
<option v-for="organization in organizations" :key="organization.id" :value="organization.id">{{ organization.name }}</option> <option v-for="organization in organizations" :key="organization.id" :value="organization.id">{{ organization.name }}</option>
</select-input> </select-input>
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" /> <text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" /> <text-input v-model="form.phone" :error="form.errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" /> <text-input v-model="form.address" :error="form.errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" /> <text-input v-model="form.city" :error="form.errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" /> <text-input v-model="form.region" :error="form.errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country"> <select-input v-model="form.country" :error="form.errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<option :value="null" /> <option :value="null" />
<option value="CA">Canada</option> <option value="CA">Canada</option>
<option value="US">United States</option> <option value="US">United States</option>
</select-input> </select-input>
<text-input v-model="form.postal_code" :error="errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" /> <text-input v-model="form.postal_code" :error="form.errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
</div> </div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex items-center"> <div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex items-center">
<button v-if="!contact.deleted_at" class="text-red-600 hover:underline" tabindex="-1" type="button" @click="destroy">Delete Contact</button> <button v-if="!contact.deleted_at" class="text-red-600 hover:underline" tabindex="-1" type="button" @click="destroy">Delete Contact</button>
<loading-button :loading="sending" class="btn-indigo ml-auto" type="submit">Update Contact</loading-button> <loading-button :loading="form.processing" class="btn-indigo ml-auto" type="submit">Update Contact</loading-button>
</div> </div>
</form> </form>
</div> </div>
@ -40,9 +40,9 @@
<script> <script>
import Layout from '@/Shared/Layout' import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput' import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
import TrashedMessage from '@/Shared/TrashedMessage' import TrashedMessage from '@/Shared/TrashedMessage'
export default { export default {
@ -51,13 +51,13 @@ export default {
title: `${this.form.first_name} ${this.form.last_name}`, title: `${this.form.first_name} ${this.form.last_name}`,
} }
}, },
layout: Layout,
components: { components: {
LoadingButton, LoadingButton,
SelectInput, SelectInput,
TextInput, TextInput,
TrashedMessage, TrashedMessage,
}, },
layout: Layout,
props: { props: {
errors: Object, errors: Object,
contact: Object, contact: Object,
@ -66,8 +66,7 @@ export default {
remember: 'form', remember: 'form',
data() { data() {
return { return {
sending: false, form: this.$inertia.form({
form: {
first_name: this.contact.first_name, first_name: this.contact.first_name,
last_name: this.contact.last_name, last_name: this.contact.last_name,
organization_id: this.contact.organization_id, organization_id: this.contact.organization_id,
@ -78,15 +77,12 @@ export default {
region: this.contact.region, region: this.contact.region,
country: this.contact.country, country: this.contact.country,
postal_code: this.contact.postal_code, postal_code: this.contact.postal_code,
}, }),
} }
}, },
methods: { methods: {
submit() { update() {
this.$inertia.put(this.route('contacts.update', this.contact.id), this.form, { this.form.put(this.route('contacts.update', this.contact.id))
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
}, },
destroy() { destroy() {
if (confirm('Are you sure you want to delete this contact?')) { if (confirm('Are you sure you want to delete this contact?')) {

View file

@ -15,8 +15,8 @@
<span class="hidden md:inline">Contact</span> <span class="hidden md:inline">Contact</span>
</inertia-link> </inertia-link>
</div> </div>
<div class="bg-white rounded shadow overflow-x-auto"> <div class="bg-white rounded-md shadow overflow-x-auto">
<table class="w-full whitespace-no-wrap"> <table class="w-full whitespace-nowrap">
<tr class="text-left font-bold"> <tr class="text-left font-bold">
<th class="px-6 pt-6 pb-4">Name</th> <th class="px-6 pt-6 pb-4">Name</th>
<th class="px-6 pt-6 pb-4">Organization</th> <th class="px-6 pt-6 pb-4">Organization</th>
@ -58,27 +58,27 @@
</tr> </tr>
</table> </table>
</div> </div>
<pagination :links="contacts.links" /> <pagination class="mt-6" :links="contacts.links" />
</div> </div>
</template> </template>
<script> <script>
import Icon from '@/Shared/Icon' import Icon from '@/Shared/Icon'
import pickBy from 'lodash/pickBy'
import Layout from '@/Shared/Layout' import Layout from '@/Shared/Layout'
import throttle from 'lodash/throttle'
import mapValues from 'lodash/mapValues' import mapValues from 'lodash/mapValues'
import Pagination from '@/Shared/Pagination' import Pagination from '@/Shared/Pagination'
import pickBy from 'lodash/pickBy'
import SearchFilter from '@/Shared/SearchFilter' import SearchFilter from '@/Shared/SearchFilter'
import throttle from 'lodash/throttle'
export default { export default {
metaInfo: { title: 'Contacts' }, metaInfo: { title: 'Contacts' },
layout: Layout,
components: { components: {
Icon, Icon,
Pagination, Pagination,
SearchFilter, SearchFilter,
}, },
layout: Layout,
props: { props: {
contacts: Object, contacts: Object,
filters: Object, filters: Object,

View file

@ -4,24 +4,24 @@
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('organizations')">Organizations</inertia-link> <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 <span class="text-indigo-400 font-medium">/</span> Create
</h1> </h1>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl"> <div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit"> <form @submit.prevent="store">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap"> <div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.name" :error="errors.name" class="pr-6 pb-8 w-full lg:w-1/2" label="Name" /> <text-input v-model="form.name" :error="form.errors.name" class="pr-6 pb-8 w-full lg:w-1/2" label="Name" />
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" /> <text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" /> <text-input v-model="form.phone" :error="form.errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" /> <text-input v-model="form.address" :error="form.errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" /> <text-input v-model="form.city" :error="form.errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" /> <text-input v-model="form.region" :error="form.errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country"> <select-input v-model="form.country" :error="form.errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<option :value="null" /> <option :value="null" />
<option value="CA">Canada</option> <option value="CA">Canada</option>
<option value="US">United States</option> <option value="US">United States</option>
</select-input> </select-input>
<text-input v-model="form.postal_code" :error="errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" /> <text-input v-model="form.postal_code" :error="form.errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
</div> </div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex justify-end items-center"> <div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex justify-end items-center">
<loading-button :loading="sending" class="btn-indigo" type="submit">Create Organization</loading-button> <loading-button :loading="form.processing" class="btn-indigo" type="submit">Create Organization</loading-button>
</div> </div>
</form> </form>
</div> </div>
@ -30,26 +30,22 @@
<script> <script>
import Layout from '@/Shared/Layout' import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput' import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
export default { export default {
metaInfo: { title: 'Create Organization' }, metaInfo: { title: 'Create Organization' },
layout: Layout,
components: { components: {
LoadingButton, LoadingButton,
SelectInput, SelectInput,
TextInput, TextInput,
}, },
props: { layout: Layout,
errors: Object,
},
remember: 'form', remember: 'form',
data() { data() {
return { return {
sending: false, form: this.$inertia.form({
form: {
name: null, name: null,
email: null, email: null,
phone: null, phone: null,
@ -58,15 +54,12 @@ export default {
region: null, region: null,
country: null, country: null,
postal_code: null, postal_code: null,
}, }),
} }
}, },
methods: { methods: {
submit() { store() {
this.$inertia.post(this.route('organizations.store'), this.form, { this.form.post(this.route('organizations.store'))
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
}, },
}, },
} }

View file

@ -8,31 +8,31 @@
<trashed-message v-if="organization.deleted_at" class="mb-6" @restore="restore"> <trashed-message v-if="organization.deleted_at" class="mb-6" @restore="restore">
This organization has been deleted. This organization has been deleted.
</trashed-message> </trashed-message>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl"> <div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit"> <form @submit.prevent="update">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap"> <div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.name" :error="errors.name" class="pr-6 pb-8 w-full lg:w-1/2" label="Name" /> <text-input v-model="form.name" :error="form.errors.name" class="pr-6 pb-8 w-full lg:w-1/2" label="Name" />
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" /> <text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.phone" :error="errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" /> <text-input v-model="form.phone" :error="form.errors.phone" class="pr-6 pb-8 w-full lg:w-1/2" label="Phone" />
<text-input v-model="form.address" :error="errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" /> <text-input v-model="form.address" :error="form.errors.address" class="pr-6 pb-8 w-full lg:w-1/2" label="Address" />
<text-input v-model="form.city" :error="errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" /> <text-input v-model="form.city" :error="form.errors.city" class="pr-6 pb-8 w-full lg:w-1/2" label="City" />
<text-input v-model="form.region" :error="errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" /> <text-input v-model="form.region" :error="form.errors.region" class="pr-6 pb-8 w-full lg:w-1/2" label="Province/State" />
<select-input v-model="form.country" :error="errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country"> <select-input v-model="form.country" :error="form.errors.country" class="pr-6 pb-8 w-full lg:w-1/2" label="Country">
<option :value="null" /> <option :value="null" />
<option value="CA">Canada</option> <option value="CA">Canada</option>
<option value="US">United States</option> <option value="US">United States</option>
</select-input> </select-input>
<text-input v-model="form.postal_code" :error="errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" /> <text-input v-model="form.postal_code" :error="form.errors.postal_code" class="pr-6 pb-8 w-full lg:w-1/2" label="Postal code" />
</div> </div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex items-center"> <div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex items-center">
<button v-if="!organization.deleted_at" class="text-red-600 hover:underline" tabindex="-1" type="button" @click="destroy">Delete Organization</button> <button v-if="!organization.deleted_at" class="text-red-600 hover:underline" tabindex="-1" type="button" @click="destroy">Delete Organization</button>
<loading-button :loading="sending" class="btn-indigo ml-auto" type="submit">Update Organization</loading-button> <loading-button :loading="form.processing" class="btn-indigo ml-auto" type="submit">Update Organization</loading-button>
</div> </div>
</form> </form>
</div> </div>
<h2 class="mt-12 font-bold text-2xl">Contacts</h2> <h2 class="mt-12 font-bold text-2xl">Contacts</h2>
<div class="mt-6 bg-white rounded shadow overflow-x-auto"> <div class="mt-6 bg-white rounded shadow overflow-x-auto">
<table class="w-full whitespace-no-wrap"> <table class="w-full whitespace-nowrap">
<tr class="text-left font-bold"> <tr class="text-left font-bold">
<th class="px-6 pt-6 pb-4">Name</th> <th class="px-6 pt-6 pb-4">Name</th>
<th class="px-6 pt-6 pb-4">City</th> <th class="px-6 pt-6 pb-4">City</th>
@ -72,16 +72,15 @@
<script> <script>
import Icon from '@/Shared/Icon' import Icon from '@/Shared/Icon'
import Layout from '@/Shared/Layout' import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput' import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
import TrashedMessage from '@/Shared/TrashedMessage' import TrashedMessage from '@/Shared/TrashedMessage'
export default { export default {
metaInfo() { metaInfo() {
return { title: this.form.name } return { title: this.form.name }
}, },
layout: Layout,
components: { components: {
Icon, Icon,
LoadingButton, LoadingButton,
@ -89,15 +88,14 @@ export default {
TextInput, TextInput,
TrashedMessage, TrashedMessage,
}, },
layout: Layout,
props: { props: {
errors: Object,
organization: Object, organization: Object,
}, },
remember: 'form', remember: 'form',
data() { data() {
return { return {
sending: false, form: this.$inertia.form({
form: {
name: this.organization.name, name: this.organization.name,
email: this.organization.email, email: this.organization.email,
phone: this.organization.phone, phone: this.organization.phone,
@ -106,15 +104,12 @@ export default {
region: this.organization.region, region: this.organization.region,
country: this.organization.country, country: this.organization.country,
postal_code: this.organization.postal_code, postal_code: this.organization.postal_code,
}, }),
} }
}, },
methods: { methods: {
submit() { update() {
this.$inertia.put(this.route('organizations.update', this.organization.id), this.form, { this.form.put(this.route('organizations.update', this.organization.id))
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
}, },
destroy() { destroy() {
if (confirm('Are you sure you want to delete this organization?')) { if (confirm('Are you sure you want to delete this organization?')) {

View file

@ -15,8 +15,8 @@
<span class="hidden md:inline">Organization</span> <span class="hidden md:inline">Organization</span>
</inertia-link> </inertia-link>
</div> </div>
<div class="bg-white rounded shadow overflow-x-auto"> <div class="bg-white rounded-md shadow overflow-x-auto">
<table class="w-full whitespace-no-wrap"> <table class="w-full whitespace-nowrap">
<tr class="text-left font-bold"> <tr class="text-left font-bold">
<th class="px-6 pt-6 pb-4">Name</th> <th class="px-6 pt-6 pb-4">Name</th>
<th class="px-6 pt-6 pb-4">City</th> <th class="px-6 pt-6 pb-4">City</th>
@ -50,27 +50,27 @@
</tr> </tr>
</table> </table>
</div> </div>
<pagination :links="organizations.links" /> <pagination class="mt-6" :links="organizations.links" />
</div> </div>
</template> </template>
<script> <script>
import Icon from '@/Shared/Icon' import Icon from '@/Shared/Icon'
import pickBy from 'lodash/pickBy'
import Layout from '@/Shared/Layout' import Layout from '@/Shared/Layout'
import throttle from 'lodash/throttle'
import mapValues from 'lodash/mapValues' import mapValues from 'lodash/mapValues'
import Pagination from '@/Shared/Pagination' import Pagination from '@/Shared/Pagination'
import pickBy from 'lodash/pickBy'
import SearchFilter from '@/Shared/SearchFilter' import SearchFilter from '@/Shared/SearchFilter'
import throttle from 'lodash/throttle'
export default { export default {
metaInfo: { title: 'Organizations' }, metaInfo: { title: 'Organizations' },
layout: Layout,
components: { components: {
Icon, Icon,
Pagination, Pagination,
SearchFilter, SearchFilter,
}, },
layout: Layout,
props: { props: {
organizations: Object, organizations: Object,
filters: Object, filters: Object,

View file

@ -4,21 +4,21 @@
<inertia-link class="text-indigo-400 hover:text-indigo-600" :href="route('users')">Users</inertia-link> <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 <span class="text-indigo-400 font-medium">/</span> Create
</h1> </h1>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl"> <div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit"> <form @submit.prevent="store">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap"> <div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.first_name" :error="errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" /> <text-input v-model="form.first_name" :error="form.errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" /> <text-input v-model="form.last_name" :error="form.errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" /> <text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.password" :error="errors.password" class="pr-6 pb-8 w-full lg:w-1/2" type="password" autocomplete="new-password" label="Password" /> <text-input v-model="form.password" :error="form.errors.password" class="pr-6 pb-8 w-full lg:w-1/2" type="password" autocomplete="new-password" label="Password" />
<select-input v-model="form.owner" :error="errors.owner" class="pr-6 pb-8 w-full lg:w-1/2" label="Owner"> <select-input v-model="form.owner" :error="form.errors.owner" class="pr-6 pb-8 w-full lg:w-1/2" label="Owner">
<option :value="true">Yes</option> <option :value="true">Yes</option>
<option :value="false">No</option> <option :value="false">No</option>
</select-input> </select-input>
<file-input v-model="form.photo" :error="errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" /> <file-input v-model="form.photo" :error="form.errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" />
</div> </div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex justify-end items-center"> <div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex justify-end items-center">
<loading-button :loading="sending" class="btn-indigo" type="submit">Create User</loading-button> <loading-button :loading="form.processing" class="btn-indigo" type="submit">Create User</loading-button>
</div> </div>
</form> </form>
</div> </div>
@ -27,51 +27,36 @@
<script> <script>
import Layout from '@/Shared/Layout' 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 FileInput from '@/Shared/FileInput'
import TextInput from '@/Shared/TextInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
export default { export default {
metaInfo: { title: 'Create User' }, metaInfo: { title: 'Create User' },
layout: Layout,
components: { components: {
FileInput,
LoadingButton, LoadingButton,
SelectInput, SelectInput,
TextInput, TextInput,
FileInput,
},
props: {
errors: Object,
}, },
layout: Layout,
remember: 'form', remember: 'form',
data() { data() {
return { return {
sending: false, form: this.$inertia.form({
form: {
first_name: null, first_name: null,
last_name: null, last_name: null,
email: null, email: null,
password: null, password: null,
owner: false, owner: false,
photo: null, photo: null,
}, }),
} }
}, },
methods: { methods: {
submit() { store() {
const data = new FormData() this.form.post(this.route('users.store'))
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, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
})
}, },
}, },
} }

View file

@ -6,27 +6,27 @@
<span class="text-indigo-400 font-medium">/</span> <span class="text-indigo-400 font-medium">/</span>
{{ form.first_name }} {{ form.last_name }} {{ form.first_name }} {{ form.last_name }}
</h1> </h1>
<img v-if="user.photo" class="block w-8 h-8 rounded-full ml-4" :src="user.photo"> <img v-if="user.photo" class="block w-8 h-8 rounded-full ml-4" :src="user.photo" />
</div> </div>
<trashed-message v-if="user.deleted_at" class="mb-6" @restore="restore"> <trashed-message v-if="user.deleted_at" class="mb-6" @restore="restore">
This user has been deleted. This user has been deleted.
</trashed-message> </trashed-message>
<div class="bg-white rounded shadow overflow-hidden max-w-3xl"> <div class="bg-white rounded-md shadow overflow-hidden max-w-3xl">
<form @submit.prevent="submit"> <form @submit.prevent="update">
<div class="p-8 -mr-6 -mb-8 flex flex-wrap"> <div class="p-8 -mr-6 -mb-8 flex flex-wrap">
<text-input v-model="form.first_name" :error="errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" /> <text-input v-model="form.first_name" :error="form.errors.first_name" class="pr-6 pb-8 w-full lg:w-1/2" label="First name" />
<text-input v-model="form.last_name" :error="errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" /> <text-input v-model="form.last_name" :error="form.errors.last_name" class="pr-6 pb-8 w-full lg:w-1/2" label="Last name" />
<text-input v-model="form.email" :error="errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" /> <text-input v-model="form.email" :error="form.errors.email" class="pr-6 pb-8 w-full lg:w-1/2" label="Email" />
<text-input v-model="form.password" :error="errors.password" class="pr-6 pb-8 w-full lg:w-1/2" type="password" autocomplete="new-password" label="Password" /> <text-input v-model="form.password" :error="form.errors.password" class="pr-6 pb-8 w-full lg:w-1/2" type="password" autocomplete="new-password" label="Password" />
<select-input v-model="form.owner" :error="errors.owner" class="pr-6 pb-8 w-full lg:w-1/2" label="Owner"> <select-input v-model="form.owner" :error="form.errors.owner" class="pr-6 pb-8 w-full lg:w-1/2" label="Owner">
<option :value="true">Yes</option> <option :value="true">Yes</option>
<option :value="false">No</option> <option :value="false">No</option>
</select-input> </select-input>
<file-input v-model="form.photo" :error="errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" /> <file-input v-model="form.photo" :error="form.errors.photo" class="pr-6 pb-8 w-full lg:w-1/2" type="file" accept="image/*" label="Photo" />
</div> </div>
<div class="px-8 py-4 bg-gray-100 border-t border-gray-200 flex items-center"> <div class="px-8 py-4 bg-gray-50 border-t border-gray-100 flex items-center">
<button v-if="!user.deleted_at" class="text-red-600 hover:underline" tabindex="-1" type="button" @click="destroy">Delete User</button> <button v-if="!user.deleted_at" class="text-red-600 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> <loading-button :loading="form.processing" class="btn-indigo ml-auto" type="submit">Update User</loading-button>
</div> </div>
</form> </form>
</div> </div>
@ -35,10 +35,10 @@
<script> <script>
import Layout from '@/Shared/Layout' import Layout from '@/Shared/Layout'
import LoadingButton from '@/Shared/LoadingButton'
import SelectInput from '@/Shared/SelectInput'
import TextInput from '@/Shared/TextInput' import TextInput from '@/Shared/TextInput'
import FileInput from '@/Shared/FileInput' import FileInput from '@/Shared/FileInput'
import SelectInput from '@/Shared/SelectInput'
import LoadingButton from '@/Shared/LoadingButton'
import TrashedMessage from '@/Shared/TrashedMessage' import TrashedMessage from '@/Shared/TrashedMessage'
export default { export default {
@ -47,52 +47,35 @@ export default {
title: `${this.form.first_name} ${this.form.last_name}`, title: `${this.form.first_name} ${this.form.last_name}`,
} }
}, },
layout: Layout,
components: { components: {
FileInput,
LoadingButton, LoadingButton,
SelectInput, SelectInput,
TextInput, TextInput,
FileInput,
TrashedMessage, TrashedMessage,
}, },
layout: Layout,
props: { props: {
errors: Object,
user: Object, user: Object,
}, },
remember: 'form', remember: 'form',
data() { data() {
return { return {
sending: false, form: this.$inertia.form({
form: { _method: 'put',
first_name: this.user.first_name, first_name: this.user.first_name,
last_name: this.user.last_name, last_name: this.user.last_name,
email: this.user.email, email: this.user.email,
password: this.user.password, password: null,
owner: this.user.owner, owner: this.user.owner,
photo: null, photo: null,
}, }),
} }
}, },
methods: { methods: {
submit() { update() {
var data = new FormData() this.form.post(this.route('users.update', this.user.id), {
data.append('first_name', this.form.first_name || '') onSuccess: () => this.form.reset('password', 'photo'),
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, {
onStart: () => this.sending = true,
onFinish: () => this.sending = false,
onSuccess: () => {
if (Object.keys(this.$page.errors).length === 0) {
this.form.photo = null
this.form.password = null
}
},
}) })
}, },
destroy() { destroy() {

View file

@ -21,8 +21,8 @@
<span class="hidden md:inline">User</span> <span class="hidden md:inline">User</span>
</inertia-link> </inertia-link>
</div> </div>
<div class="bg-white rounded shadow overflow-x-auto"> <div class="bg-white rounded-md shadow overflow-x-auto">
<table class="w-full whitespace-no-wrap"> <table class="w-full whitespace-nowrap">
<tr class="text-left font-bold"> <tr class="text-left font-bold">
<th class="px-6 pt-6 pb-4">Name</th> <th class="px-6 pt-6 pb-4">Name</th>
<th class="px-6 pt-6 pb-4">Email</th> <th class="px-6 pt-6 pb-4">Email</th>
@ -31,7 +31,7 @@
<tr v-for="user in users" :key="user.id" class="hover:bg-gray-100 focus-within:bg-gray-100"> <tr v-for="user in users" :key="user.id" class="hover:bg-gray-100 focus-within:bg-gray-100">
<td class="border-t"> <td class="border-t">
<inertia-link class="px-6 py-4 flex items-center focus:text-indigo-500" :href="route('users.edit', user.id)"> <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"> <img v-if="user.photo" class="block w-5 h-5 rounded-full mr-2 -my-2" :src="user.photo" />
{{ user.name }} {{ user.name }}
<icon v-if="user.deleted_at" name="trash" class="flex-shrink-0 w-3 h-3 fill-gray-400 ml-2" /> <icon v-if="user.deleted_at" name="trash" class="flex-shrink-0 w-3 h-3 fill-gray-400 ml-2" />
</inertia-link> </inertia-link>
@ -62,19 +62,19 @@
<script> <script>
import Icon from '@/Shared/Icon' import Icon from '@/Shared/Icon'
import Layout from '@/Shared/Layout'
import mapValues from 'lodash/mapValues'
import pickBy from 'lodash/pickBy' import pickBy from 'lodash/pickBy'
import SearchFilter from '@/Shared/SearchFilter' import Layout from '@/Shared/Layout'
import throttle from 'lodash/throttle' import throttle from 'lodash/throttle'
import mapValues from 'lodash/mapValues'
import SearchFilter from '@/Shared/SearchFilter'
export default { export default {
metaInfo: { title: 'Users' }, metaInfo: { title: 'Users' },
layout: Layout,
components: { components: {
Icon, Icon,
SearchFilter, SearchFilter,
}, },
layout: Layout,
props: { props: {
users: Array, users: Array,
filters: Object, filters: Object,

View file

@ -52,7 +52,7 @@ export default {
}, },
}, },
mounted() { mounted() {
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', e => {
if (e.keyCode === 27) { if (e.keyCode === 27) {
this.show = false this.show = false
} }

View file

@ -2,14 +2,16 @@
<div> <div>
<label v-if="label" class="form-label">{{ label }}:</label> <label v-if="label" class="form-label">{{ label }}:</label>
<div class="form-input p-0" :class="{ error: errors.length }"> <div class="form-input p-0" :class="{ error: errors.length }">
<input ref="file" type="file" :accept="accept" class="hidden" @change="change"> <input ref="file" type="file" :accept="accept" class="hidden" @change="change" />
<div v-if="!value" class="p-2"> <div v-if="!value" class="p-2">
<button type="button" class="px-4 py-1 bg-gray-500 hover:bg-gray-700 rounded-sm text-xs font-medium text-white" @click="browse"> <button type="button" class="px-4 py-1 bg-gray-500 hover:bg-gray-700 rounded-sm text-xs font-medium text-white" @click="browse">
Browse Browse
</button> </button>
</div> </div>
<div v-else class="flex items-center justify-between p-2"> <div v-else class="flex items-center justify-between p-2">
<div class="flex-1 pr-1">{{ value.name }} <span class="text-gray-500 text-xs">({{ filesize(value.size) }})</span></div> <div class="flex-1 pr-1">
{{ value.name }} <span class="text-gray-500 text-xs">({{ filesize(value.size) }})</span>
</div>
<button type="button" class="px-4 py-1 bg-gray-500 hover:bg-gray-700 rounded-sm text-xs font-medium text-white" @click="remove"> <button type="button" class="px-4 py-1 bg-gray-500 hover:bg-gray-700 rounded-sm text-xs font-medium text-white" @click="remove">
Remove Remove
</button> </button>
@ -40,7 +42,7 @@ export default {
methods: { methods: {
filesize(size) { filesize(size) {
var i = Math.floor(Math.log(size) / Math.log(1024)) 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] return (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i]
}, },
browse() { browse() {
this.$refs.file.click() this.$refs.file.click()

View file

@ -1,21 +1,21 @@
<template> <template>
<div> <div>
<div v-if="$page.flash.success && show" class="mb-8 flex items-center justify-between bg-green-500 rounded max-w-3xl"> <div v-if="$page.props.flash.success && show" class="mb-8 flex items-center justify-between bg-green-500 rounded max-w-3xl">
<div class="flex items-center"> <div class="flex items-center">
<svg class="ml-4 mr-2 flex-shrink-0 w-4 h-4 fill-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><polygon points="0 11 2 9 7 14 18 3 20 5 7 18" /></svg> <svg class="ml-4 mr-2 flex-shrink-0 w-4 h-4 fill-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><polygon points="0 11 2 9 7 14 18 3 20 5 7 18" /></svg>
<div class="py-4 text-white text-sm font-medium">{{ $page.flash.success }}</div> <div class="py-4 text-white text-sm font-medium">{{ $page.props.flash.success }}</div>
</div> </div>
<button type="button" class="group mr-2 p-2" @click="show = false"> <button type="button" class="group mr-2 p-2" @click="show = false">
<svg class="block w-2 h-2 fill-green-800 group-hover:fill-white" xmlns="http://www.w3.org/2000/svg" width="235.908" height="235.908" viewBox="278.046 126.846 235.908 235.908"><path d="M506.784 134.017c-9.56-9.56-25.06-9.56-34.62 0L396 210.18l-76.164-76.164c-9.56-9.56-25.06-9.56-34.62 0-9.56 9.56-9.56 25.06 0 34.62L361.38 244.8l-76.164 76.165c-9.56 9.56-9.56 25.06 0 34.62 9.56 9.56 25.06 9.56 34.62 0L396 279.42l76.164 76.165c9.56 9.56 25.06 9.56 34.62 0 9.56-9.56 9.56-25.06 0-34.62L430.62 244.8l76.164-76.163c9.56-9.56 9.56-25.06 0-34.62z" /></svg> <svg class="block w-2 h-2 fill-green-800 group-hover:fill-white" xmlns="http://www.w3.org/2000/svg" width="235.908" height="235.908" viewBox="278.046 126.846 235.908 235.908"><path d="M506.784 134.017c-9.56-9.56-25.06-9.56-34.62 0L396 210.18l-76.164-76.164c-9.56-9.56-25.06-9.56-34.62 0-9.56 9.56-9.56 25.06 0 34.62L361.38 244.8l-76.164 76.165c-9.56 9.56-9.56 25.06 0 34.62 9.56 9.56 25.06 9.56 34.62 0L396 279.42l76.164 76.165c9.56 9.56 25.06 9.56 34.62 0 9.56-9.56 9.56-25.06 0-34.62L430.62 244.8l76.164-76.163c9.56-9.56 9.56-25.06 0-34.62z" /></svg>
</button> </button>
</div> </div>
<div v-if="($page.flash.error || Object.keys($page.errors).length > 0) && show" class="mb-8 flex items-center justify-between bg-red-500 rounded max-w-3xl"> <div v-if="($page.props.flash.error || Object.keys($page.props.errors).length > 0) && show" class="mb-8 flex items-center justify-between bg-red-400 rounded max-w-3xl">
<div class="flex items-center"> <div class="flex items-center">
<svg class="ml-4 mr-2 flex-shrink-0 w-4 h-4 fill-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm1.41-1.41A8 8 0 1 0 15.66 4.34 8 8 0 0 0 4.34 15.66zm9.9-8.49L11.41 10l2.83 2.83-1.41 1.41L10 11.41l-2.83 2.83-1.41-1.41L8.59 10 5.76 7.17l1.41-1.41L10 8.59l2.83-2.83 1.41 1.41z" /></svg> <svg class="ml-4 mr-2 flex-shrink-0 w-4 h-4 fill-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.93 17.07A10 10 0 1 1 17.07 2.93 10 10 0 0 1 2.93 17.07zm1.41-1.41A8 8 0 1 0 15.66 4.34 8 8 0 0 0 4.34 15.66zm9.9-8.49L11.41 10l2.83 2.83-1.41 1.41L10 11.41l-2.83 2.83-1.41-1.41L8.59 10 5.76 7.17l1.41-1.41L10 8.59l2.83-2.83 1.41 1.41z" /></svg>
<div v-if="$page.flash.error" class="py-4 text-white text-sm font-medium">{{ $page.flash.error }}</div> <div v-if="$page.props.flash.error" class="py-4 text-white text-sm font-medium">{{ $page.props.flash.error }}</div>
<div v-else class="py-4 text-white text-sm font-medium"> <div v-else class="py-4 text-white text-sm font-medium">
<span v-if="Object.keys($page.errors).length === 1">There is one form error.</span> <span v-if="Object.keys($page.props.errors).length === 1">There is one form error.</span>
<span v-else>There are {{ Object.keys($page.errors).length }} form errors.</span> <span v-else>There are {{ Object.keys($page.props.errors).length }} form errors.</span>
</div> </div>
</div> </div>
<button type="button" class="group mr-2 p-2" @click="show = false"> <button type="button" class="group mr-2 p-2" @click="show = false">
@ -33,7 +33,7 @@ export default {
} }
}, },
watch: { watch: {
'$page.flash': { '$page.props.flash': {
handler() { handler() {
this.show = true this.show = true
}, },

View file

@ -2,7 +2,7 @@
<div> <div>
<portal-target name="dropdown" slim /> <portal-target name="dropdown" slim />
<div class="md:flex md:flex-col"> <div class="md:flex md:flex-col">
<div class="md:h-screen md:flex md:flex-col" @click="hideDropdownMenus"> <div class="md:h-screen md:flex md:flex-col">
<div class="md:flex md:flex-shrink-0"> <div class="md:flex md:flex-shrink-0">
<div class="bg-indigo-900 md:flex-shrink-0 md:w-56 px-6 py-4 flex items-center justify-between md:justify-center"> <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="/"> <inertia-link class="mt-1" href="/">
@ -11,30 +11,30 @@
<dropdown class="md:hidden" placement="bottom-end"> <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> <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-lg bg-indigo-800 rounded"> <div slot="dropdown" class="mt-2 px-8 py-4 shadow-lg bg-indigo-800 rounded">
<main-menu :url="url()" /> <main-menu />
</div> </div>
</dropdown> </dropdown>
</div> </div>
<div class="bg-white border-b w-full p-4 md:py-0 md:px-12 text-sm md:text-md flex justify-between items-center"> <div class="bg-white border-b w-full p-4 md:py-0 md:px-12 text-sm md:text-md flex justify-between items-center">
<div class="mt-1 mr-4">{{ $page.auth.user.account.name }}</div> <div class="mt-1 mr-4">{{ $page.props.auth.user.account.name }}</div>
<dropdown class="mt-1" placement="bottom-end"> <dropdown class="mt-1" placement="bottom-end">
<div class="flex items-center cursor-pointer select-none group"> <div class="flex items-center cursor-pointer select-none group">
<div class="text-gray-700 group-hover:text-indigo-600 focus:text-indigo-600 mr-1 whitespace-no-wrap"> <div class="text-gray-700 group-hover:text-indigo-600 focus:text-indigo-600 mr-1 whitespace-nowrap">
<span>{{ $page.auth.user.first_name }}</span> <span>{{ $page.props.auth.user.first_name }}</span>
<span class="hidden md:inline">{{ $page.auth.user.last_name }}</span> <span class="hidden md:inline">{{ $page.props.auth.user.last_name }}</span>
</div> </div>
<icon class="w-5 h-5 group-hover:fill-indigo-600 fill-gray-700 focus:fill-indigo-600" name="cheveron-down" /> <icon class="w-5 h-5 group-hover:fill-indigo-600 fill-gray-700 focus:fill-indigo-600" name="cheveron-down" />
</div> </div>
<div slot="dropdown" class="mt-2 py-2 shadow-xl bg-white rounded text-sm"> <div slot="dropdown" class="mt-2 py-2 shadow-xl bg-white rounded text-sm">
<inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white" :href="route('users.edit', $page.auth.user.id)">My Profile</inertia-link> <inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white" :href="route('users.edit', $page.props.auth.user.id)">My Profile</inertia-link>
<inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white" :href="route('users')">Manage Users</inertia-link> <inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white" :href="route('users')">Manage Users</inertia-link>
<inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white" :href="route('logout')" method="post">Logout</inertia-link> <inertia-link class="block px-6 py-2 hover:bg-indigo-500 hover:text-white w-full text-left" :href="route('logout')" method="post" as="button">Logout</inertia-link>
</div> </div>
</dropdown> </dropdown>
</div> </div>
</div> </div>
<div class="md:flex md:flex-grow md:overflow-hidden"> <div class="md:flex md:flex-grow md:overflow-hidden">
<main-menu :url="url()" class="hidden md:block bg-indigo-800 flex-shrink-0 w-56 p-12 overflow-y-auto" /> <main-menu class="hidden md:block bg-indigo-800 flex-shrink-0 w-56 p-12 overflow-y-auto" />
<div class="md:flex-1 px-4 py-8 md:p-12 md:overflow-y-auto" scroll-region> <div class="md:flex-1 px-4 py-8 md:p-12 md:overflow-y-auto" scroll-region>
<flash-messages /> <flash-messages />
<slot /> <slot />
@ -46,11 +46,11 @@
</template> </template>
<script> <script>
import Dropdown from '@/Shared/Dropdown'
import FlashMessages from '@/Shared/FlashMessages'
import Icon from '@/Shared/Icon' import Icon from '@/Shared/Icon'
import Logo from '@/Shared/Logo' import Logo from '@/Shared/Logo'
import Dropdown from '@/Shared/Dropdown'
import MainMenu from '@/Shared/MainMenu' import MainMenu from '@/Shared/MainMenu'
import FlashMessages from '@/Shared/FlashMessages'
export default { export default {
components: { components: {
@ -60,19 +60,5 @@ export default {
Logo, Logo,
MainMenu, MainMenu,
}, },
data() {
return {
showUserMenu: false,
accounts: null,
}
},
methods: {
url() {
return location.pathname.substr(1)
},
hideDropdownMenus() {
this.showUserMenu = false
},
},
} }
</script> </script>

View file

@ -34,16 +34,13 @@ export default {
components: { components: {
Icon, Icon,
}, },
props: {
url: String,
},
methods: { methods: {
isUrl(...urls) { isUrl(...urls) {
let currentUrl = this.$page.url.substr(1)
if (urls[0] === '') { if (urls[0] === '') {
return this.url === '' return currentUrl === ''
} }
return urls.filter(url => currentUrl.startsWith(url)).length
return urls.filter(url => this.url.startsWith(url)).length
}, },
}, },
} }

View file

@ -1,9 +1,11 @@
<template> <template>
<div class="mt-6 -mb-1 flex flex-wrap"> <div v-if="links.length > 3">
<template v-for="(link, key) in links"> <div class="flex flex-wrap -mb-1">
<div v-if="link.url === null" :key="key" class="mr-1 mb-1 px-4 py-3 text-sm border rounded text-gray-400" :class="{ 'ml-auto': link.label === 'Next' }">{{ link.label }}</div> <template v-for="(link, key) in links">
<inertia-link v-else :key="key" class="mr-1 mb-1 px-4 py-3 text-sm border rounded hover:bg-white focus:border-indigo-500 focus:text-indigo-500" :class="{ 'bg-white': link.active, 'ml-auto': link.label === 'Next' }" :href="link.url">{{ link.label }}</inertia-link> <div v-if="link.url === null" :key="key" class="mr-1 mb-1 px-4 py-3 text-sm leading-4 text-gray-400 border rounded" v-html="link.label" />
</template> <inertia-link v-else :key="key" class="mr-1 mb-1 px-4 py-3 text-sm leading-4 border rounded hover:bg-white focus:border-indigo-500 focus:text-indigo-500" :class="{ 'bg-white': link.active }" :href="link.url" v-html="link.label" />
</template>
</div>
</div> </div>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="flex items-center"> <div class="flex items-center">
<div class="flex w-full bg-white shadow rounded"> <div class="flex w-full bg-white shadow rounded">
<dropdown :auto-close="false" class="px-4 md:px-6 rounded-l border-r hover:bg-gray-100 focus:border-white focus:shadow-outline focus:z-10" placement="bottom-start"> <dropdown :auto-close="false" class="px-4 md:px-6 rounded-l border-r hover:bg-gray-100 focus:border-white focus:ring focus:z-10" placement="bottom-start">
<div class="flex items-baseline"> <div class="flex items-baseline">
<span class="text-gray-700 hidden md:inline">Filter</span> <span class="text-gray-700 hidden md:inline">Filter</span>
<svg class="w-2 h-2 fill-gray-700 md:ml-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 961.243 599.998"> <svg class="w-2 h-2 fill-gray-700 md:ml-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 961.243 599.998">
@ -12,7 +12,7 @@
<slot /> <slot />
</div> </div>
</dropdown> </dropdown>
<input class="relative w-full px-6 py-3 rounded-r focus:shadow-outline" autocomplete="off" type="text" name="search" placeholder="Search…" :value="value" @input="$emit('input', $event.target.value)"> <input class="relative w-full px-6 py-3 rounded-r focus:ring" autocomplete="off" type="text" name="search" placeholder="Search…" :value="value" @input="$emit('input', $event.target.value)" />
</div> </div>
<button class="ml-3 text-sm text-gray-500 hover:text-gray-700 focus:text-indigo-500" type="button" @click="$emit('reset')">Reset</button> <button class="ml-3 text-sm text-gray-500 hover:text-gray-700 focus:text-indigo-500" type="button" @click="$emit('reset')">Reset</button>
</div> </div>

View file

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<label v-if="label" class="form-label" :for="id">{{ label }}:</label> <label v-if="label" class="form-label" :for="id">{{ label }}:</label>
<input :id="id" ref="input" v-bind="$attrs" class="form-input" :class="{ error: error }" :type="type" :value="value" @input="$emit('input', $event.target.value)"> <input :id="id" ref="input" v-bind="$attrs" class="form-input" :class="{ error: error }" :type="type" :value="value" @input="$emit('input', $event.target.value)" />
<div v-if="error" class="form-error">{{ error }}</div> <div v-if="error" class="form-error">{{ error }}</div>
</div> </div>
</template> </template>

View file

@ -1,5 +1,5 @@
<template> <template>
<div class="p-4 bg-yellow-400 rounded flex items-center justify-between max-w-3xl"> <div class="p-4 bg-yellow-300 rounded flex items-center justify-between max-w-3xl">
<div class="flex items-center"> <div class="flex items-center">
<icon name="trash" class="flex-shrink-0 w-4 h-4 fill-yellow-800 mr-2" /> <icon name="trash" class="flex-shrink-0 w-4 h-4 fill-yellow-800 mr-2" />
<div class="text-sm font-medium text-yellow-800"> <div class="text-sm font-medium text-yellow-800">

23
resources/js/app.js vendored
View file

@ -1,27 +1,28 @@
import Vue from 'vue' import Vue from 'vue'
import VueMeta from 'vue-meta' import VueMeta from 'vue-meta'
import PortalVue from 'portal-vue' import PortalVue from 'portal-vue'
import { InertiaApp } from '@inertiajs/inertia-vue' import { App, plugin } from '@inertiajs/inertia-vue'
import { InertiaProgress } from '@inertiajs/progress/src' import { InertiaProgress } from '@inertiajs/progress/src'
Vue.config.productionTip = false Vue.config.productionTip = false
Vue.mixin({ methods: { route: window.route } }) Vue.mixin({ methods: { route: window.route } })
Vue.use(InertiaApp) Vue.use(plugin)
Vue.use(PortalVue) Vue.use(PortalVue)
Vue.use(VueMeta) Vue.use(VueMeta)
InertiaProgress.init() InertiaProgress.init()
let app = document.getElementById('app') const el = document.getElementById('app')
new Vue({ new Vue({
metaInfo: { metaInfo: {
titleTemplate: (title) => title ? `${title} - Ping CRM` : 'Ping CRM' titleTemplate: title => (title ? `${title} - Ping CRM` : 'Ping CRM'),
}, },
render: h => h(InertiaApp, { render: h =>
props: { h(App, {
initialPage: JSON.parse(app.dataset.page), props: {
resolveComponent: name => import(`@/Pages/${name}`).then(module => module.default), initialPage: JSON.parse(el.dataset.page),
}, resolveComponent: name => import(`@/Pages/${name}`).then(module => module.default),
}), },
}).$mount(app) }),
}).$mount(el)

View file

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html class="h-full bg-gray-200"> <html class="h-full bg-gray-100">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="{{ mix('/css/app.css') }}" rel="stylesheet"> <link href="{{ asset('css/app.css') }}" rel="stylesheet">
{{-- Inertia --}} {{-- Inertia --}}
<script src="https://polyfill.io/v3/polyfill.min.js?features=smoothscroll,NodeList.prototype.forEach,Promise,Object.values,Object.assign" defer></script> <script src="https://polyfill.io/v3/polyfill.min.js?features=smoothscroll,NodeList.prototype.forEach,Promise,Object.values,Object.assign" defer></script>

48
tailwind.config.js vendored
View file

@ -1,22 +1,42 @@
const colors = require('tailwindcss/colors')
const defaultTheme = require('tailwindcss/defaultTheme') const defaultTheme = require('tailwindcss/defaultTheme')
module.exports = { module.exports = {
purge: [
// prettier-ignore
'./resources/**/*.blade.php',
'./resources/**/*.js',
'./resources/**/*.vue',
],
darkMode: false, // or 'media' or 'class'
theme: { theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
red: colors.red,
orange: colors.orange,
yellow: colors.yellow,
green: colors.green,
gray: colors.blueGray,
indigo: {
100: '#e6e8ff',
300: '#b2b7ff',
400: '#7886d7',
500: '#6574cd',
600: '#5661b3',
800: '#2f365f',
900: '#191e38',
},
},
extend: { extend: {
borderColor: theme => ({
DEFAULT: theme('colors.gray.200', 'currentColor'),
}),
fontFamily: { fontFamily: {
sans: ['Cerebri Sans', ...defaultTheme.fontFamily.sans], sans: ['Cerebri Sans', ...defaultTheme.fontFamily.sans],
}, },
colors: {
indigo: {
'900': '#191e38',
'800': '#2f365f',
'600': '#5661b3',
'500': '#6574cd',
'400': '#7886d7',
'300': '#b2b7ff',
'100': '#e6e8ff',
},
},
boxShadow: theme => ({ boxShadow: theme => ({
outline: '0 0 0 2px ' + theme('colors.indigo.500'), outline: '0 0 0 2px ' + theme('colors.indigo.500'),
}), }),
@ -24,9 +44,9 @@ module.exports = {
}, },
}, },
variants: { variants: {
fill: ['responsive', 'hover', 'focus', 'group-hover'], extend: {
textColor: ['responsive', 'hover', 'focus', 'group-hover'], fill: ['focus', 'group-hover'],
zIndex: ['responsive', 'focus'], },
}, },
plugins: [], plugins: [],
} }

31
webpack.mix.js vendored
View file

@ -1,9 +1,7 @@
const path = require('path')
const mix = require('laravel-mix')
const cssImport = require('postcss-import') const cssImport = require('postcss-import')
const cssNesting = require('postcss-nesting') const cssNesting = require('postcss-nesting')
const mix = require('laravel-mix')
const path = require('path')
const purgecss = require('@fullhuman/postcss-purgecss')
const tailwindcss = require('tailwindcss')
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@ -16,22 +14,15 @@ const tailwindcss = require('tailwindcss')
| |
*/ */
mix.js('resources/js/app.js', 'public/js') mix
.postCss('resources/css/app.css', 'public/css/app.css') .js('resources/js/app.js', 'public/js')
.options({ .vue()
postCss: [ .postCss('resources/css/app.css', 'public/css', [
cssImport(), // prettier-ignore
cssNesting(), cssImport(),
tailwindcss('tailwind.config.js'), cssNesting(),
...mix.inProduction() ? [ require('tailwindcss'),
purgecss({ ])
content: ['./resources/views/**/*.blade.php', './resources/js/**/*.vue'],
defaultExtractor: content => content.match(/[\w-/:.]+(?<!:)/g) || [],
whitelistPatternsChildren: [/nprogress/],
}),
] : [],
],
})
.webpackConfig({ .webpackConfig({
output: { chunkFilename: 'js/[name].js?id=[chunkhash]' }, output: { chunkFilename: 'js/[name].js?id=[chunkhash]' },
resolve: { resolve: {